home *** CD-ROM | disk | FTP | other *** search
/ Aminet 20 / Aminet 20 (1997)(GTI - Schatztruhe)[!][Aug 1997].iso / Aminet / comm / www / HTP.lha / HTP / source / htp.c < prev    next >
C/C++ Source or Header  |  1997-06-21  |  112KB  |  3,867 lines

  1. /*
  2. //
  3. // htp.c
  4. //
  5. // main(), major functionality modules
  6. //
  7. // Copyright (c) 1995-96 Jim Nelson.  Permission to distribute
  8. // granted by the author.  No warranties are made on the fitness of this
  9. // source code.
  10. // Amiga version - 1997 - Geert Bevin
  11. //
  12. */
  13.  
  14. #include "htp.h"
  15.  
  16. /*
  17. // default response filename
  18. */
  19. const char *DEFAULT_RESPONSE_FILE = "htp.rsp";
  20.  
  21. /*
  22. // variable types
  23. */
  24. #define VAR_TYPE_SET_MACRO      (1)
  25. #define VAR_TYPE_BLOCK_MACRO    (2)
  26. #define VAR_TYPE_INTERNAL       (3)
  27. #define VAR_TYPE_ALTTEXT        (4)
  28. #define VAR_TYPE_DEF_MACRO      (5)
  29.  
  30. /*
  31. // variable flags
  32. */
  33. #define VAR_FLAG_NONE           (0x0000)
  34. #define VAR_FLAG_QUOTED         (0x0001)
  35.  
  36. /*
  37. // specialized markup processors type definitions
  38. */
  39.  
  40. /* MARKUP_FUNC return codes */
  41. #define MARKUP_OKAY             (0)
  42. #define MARKUP_REPLACED         (1)
  43. #define DISCARD_MARKUP          (2)
  44. #define MARKUP_ERROR            ((uint) -1)
  45. #define NEW_MARKUP              (3)
  46.  
  47. /*
  48. // when reading source files, need to dynamically allocate memory to avoid
  49. // overruns ... use a stronger strategy for "real" operating systems, more
  50. // conservative for wimpy DOS
  51. */
  52. #if __MSDOS__
  53.  
  54. #define MIN_PLAINTEXT_SIZE      (128)
  55. #define PLAINTEXT_GROW_SIZE     (32)
  56.  
  57. #else
  58.  
  59. #define MIN_PLAINTEXT_SIZE      (16 * KBYTE)
  60. #define PLAINTEXT_GROW_SIZE     (4 * KBYTE)
  61.  
  62. #endif
  63.  
  64.  
  65. /*
  66. // miscellaneous definitions
  67. */
  68. #define MAX_TIME_DATE_SIZE      (128)
  69. #define DEFAULT_PRECISION       (0)
  70. #define SEARCH_PATH_SIZE        (1024)
  71.  
  72. /*
  73. // htp task structure
  74. //
  75. // (the word "task" is not to be confused with the traditional operating system
  76. // term ... it is used here to represent all the information associated with
  77. // the particular job at hand, which is reading in a file, writing out to
  78. // a file, and maintaining information during the entire operation)
  79. */
  80. typedef struct tagTASK
  81. {
  82.     TEXTFILE        *infile;
  83.     TEXTFILE        *outfile;
  84.     VARSTORE        *varstore;
  85.     const char      *sourceFilename;
  86. } TASK;
  87.  
  88. /*
  89. // markup processor function and array association structure
  90. */
  91. typedef uint (*MARKUP_FUNC)(TASK *task, HTML_MARKUP *htmlMarkup, char **newPlaintext);
  92. typedef struct tagMARKUP_PROCESSORS
  93. {
  94.     const char      *tag;
  95.     uint            markupType;
  96.     MARKUP_FUNC     markupFunc;
  97. } MARKUP_PROCESSORS;
  98.  
  99. /*
  100. // template file name (used internally to store name for post-processing)
  101. // use squirrelly characters and a space to avoid conflicting with
  102. // user names
  103. */
  104. const char *VAR_TEMPLATE_NAME = "__!TEMPLATE FILE~";
  105.  
  106. /*
  107. // forward references
  108. */
  109. BOOL ProcessTask(TASK *task);
  110. BOOL OptionCallback(const char *name, const char *value, ULONG userParam);
  111.  
  112. /*
  113. // the user can configure what kind of characters to use to surround htp
  114. // markups, to avoid conflicts with HTML markups ... default is the standard
  115. // greater-than/less-than bracketing, but also acceptable are square
  116. // brackets and curly brackets (parentheses are just too common in normal
  117. // text to be useful)
  118. //
  119. // Because htp also processes standard HTML markups, a IS_OPEN_MARKUP and
  120. // IS_CLOSE_MARKUP macros are used instead of standard comparisons ... watch
  121. // out for side-effects
  122. //
  123. // MARKUP_TYPE_ANY is used for markup processors to define they are
  124. // interested in either kind of markup (currently unused)
  125. //
  126. // MARKUP_OPEN_DELIM and MARKUP_CLOSE_DELIM are used to return the proper
  127. // delimiter given the markup type
  128. */
  129. #define HTML_OPEN_MARKUP            ('<')
  130. #define HTML_CLOSE_MARKUP           ('>')
  131.  
  132. char htpOpenMarkup = HTML_OPEN_MARKUP;
  133. char htpCloseMarkup = HTML_CLOSE_MARKUP;
  134.  
  135. #define IS_OPEN_MARKUP(c)           (((c) == '<') || ((c) == htpOpenMarkup))
  136. #define IS_CLOSE_MARKUP(c)          (((c) == '>') || ((c) == htpCloseMarkup))
  137.  
  138. #define MARKUP_TYPE_HTML            (0x0001)
  139. #define MARKUP_TYPE_HTP             (0x0002)
  140. #define MARKUP_TYPE_ANY             (0xFFFF)
  141.  
  142. #define MARKUP_OPEN_DELIM(t) \
  143.     (((t) & MARKUP_TYPE_HTP) ? htpOpenMarkup : HTML_OPEN_MARKUP)
  144.  
  145. #define MARKUP_CLOSE_DELIM(t) \
  146.     (((t) & MARKUP_TYPE_HTP) ? htpCloseMarkup : HTML_CLOSE_MARKUP)
  147.  
  148. /*
  149. // the global variable store ... holds permanent, file-to-file macros
  150. // (these are set in the global default file) ... filename kept for
  151. // dependency checking
  152. */
  153. VARSTORE globalVarStore;
  154. char globalFilename[MAX_PATHNAME_LEN];
  155.  
  156. /*
  157. // the "project" variable store ... holds macros only for files in current
  158. // directory, or project (this is loaded from the project default file,
  159. // which is called htp.def)
  160. */
  161. VARSTORE projectVarStore;
  162. char projectFilename[MAX_PATHNAME_LEN];
  163.  
  164. /*
  165. // include file search path
  166. */
  167. char searchPath[SEARCH_PATH_SIZE] = { 0, };
  168.  
  169. /*
  170. // ALT text macro store
  171. */
  172. VARSTORE altTextVarStore;
  173.  
  174. /*
  175. // for tracking ExpandMacros performance, debug version only
  176. */
  177. #if DEBUG
  178. uint expandSkipped = 0;
  179. uint expandPerformed = 0;
  180. #endif
  181.  
  182. /*
  183. //
  184. // generic, global utility functions
  185. //
  186. */
  187.  
  188. /*
  189. // temporary file name generator
  190. */
  191.  
  192. BOOL CreateTempFilename(char *tempfilename, uint size)
  193. {
  194.     static char *tempDir;
  195.     char *tmpName;
  196.  
  197.     assert(tempfilename != NULL);
  198.  
  199.     /* find a preferred temporary directory if not found already */
  200.     if(tempDir == NULL)
  201.     {
  202.         if((tempDir = getenv("TEMP")) == NULL)
  203.         {
  204.             if((tempDir = getenv("TMP")) == NULL)
  205.             {
  206.                 tempDir = DIR_CURRENT_STRING;
  207.             }
  208.         }
  209.     }
  210.  
  211.     /* get a temporary filename */
  212.     if((tmpName = tempnam(tempDir, (char *) PROGRAM_NAME)) != NULL)
  213.     {
  214.         /* copy the filename to the callers buffer */
  215.         StringCopy(tempfilename, tmpName, size);
  216.  
  217.         /* free the tempnam buffer and return success */
  218.         free(tmpName);
  219.  
  220.         return TRUE;
  221.     }
  222.  
  223.     return FALSE;
  224. }
  225.  
  226.  
  227. #if 0
  228.  
  229. /*
  230. // extract directory from a filename, if present
  231. */
  232.  
  233. char *GetFileDirectory(const char *filename, char *directory, uint size)
  234. {
  235.     const char *filePtr;
  236.     uint len;
  237.  
  238.     *directory = NUL;
  239.  
  240.     len = strlen(filename);
  241.     if(len == 0)
  242.     {
  243.         return directory;
  244.     }
  245.  
  246.     filePtr = filename + len - 1;
  247.     while(filePtr != filename)
  248.     {
  249.         if(*filePtr == DIR_DELIMITER)
  250.         {
  251.             return StringCopy(directory, filename, (len <= size) ? len : size);
  252.         }
  253.  
  254.         filePtr--;
  255.         len--;
  256.     }
  257.  
  258.     return directory;
  259. }   
  260.  
  261. #endif
  262.  
  263. /*
  264. // ParseFilename
  265. //
  266. // Returns a pointer to the filename in a full pathname
  267. */
  268. char *FindFilename(char *pathname)
  269. {
  270.     char *filePtr;
  271.     uint len;
  272.  
  273.     assert(pathname != NULL);
  274.     if(pathname == NULL)
  275.     {
  276.         return NULL;
  277.     }
  278.  
  279.     len = strlen(pathname);
  280.     if(len == 0)
  281.     {
  282.         return pathname;
  283.     }
  284.  
  285.     filePtr = pathname + len - 1;
  286.     while(filePtr != pathname)
  287.     {
  288.         if(strchr(ALL_FILESYSTEM_DELIMITERS, *filePtr) != NULL)
  289.         {
  290.             /* found the first delimiter, return pointer to character just */
  291.             /* past this one */
  292.             /* if pathname ended in a delimiter, then this will return a */
  293.             /* pointer to NUL, which is acceptable */
  294.             return filePtr + 1;
  295.         }
  296.  
  297.         filePtr--;
  298.     }
  299.  
  300.     return pathname;
  301. }
  302.  
  303. /*
  304. // safe strncpy() wrapper (suggested by joseph.dandrea@att.com) ... strncpy()
  305. // by itself has some ugly problems, and strcpy() is simply dangerous.
  306. // Joseph recommended a macro, but I'm sticking to the bulkier solution of
  307. // using a function
  308. */
  309. char *StringCopy(char *dest, const char *src, uint size)
  310. {
  311.     assert(dest != NULL);
  312.     assert(src != NULL);
  313.  
  314.     strncpy(dest, src, size);
  315.     dest[size - 1] = NUL;
  316.  
  317.     return dest;
  318. }
  319.  
  320. /*
  321. // re-entrant string tokenizer ... used because option.c requires simultaneous
  322. // uses of strtok(), and the standard version just dont cut it
  323. */
  324. char *StringFirstToken(FIND_TOKEN *findToken, char *string, const char *tokens)
  325. {
  326.     char *ptr;
  327.  
  328.     assert(string != NULL);
  329.     assert(findToken != NULL);
  330.  
  331.     findToken->tokens = tokens;
  332.     findToken->lastChar = string + strlen(string);
  333.     findToken->nextStart = findToken->lastChar;
  334.  
  335.     if(tokens == NULL)
  336.     {
  337.         return string;
  338.     }
  339.  
  340.     if((ptr = strpbrk(string, tokens)) != NULL)
  341.     {
  342.         *ptr = NUL;
  343.         findToken->nextStart = ptr;
  344.     }
  345.  
  346.     return string;
  347. }
  348.  
  349. char *StringNextToken(FIND_TOKEN *findToken)
  350. {
  351.     char *ptr;
  352.     char *start;
  353.  
  354.     assert(findToken != NULL);
  355.     assert(findToken->lastChar != NULL);
  356.  
  357.     ptr = findToken->nextStart;
  358.  
  359.     /* check if this is the end of the original string */
  360.     if(ptr == findToken->lastChar)
  361.     {
  362.         return NULL;
  363.     }
  364.  
  365.     /* nextStart points to NUL left by last search, skip past it */
  366.     ptr++;
  367.     start = ptr;
  368.  
  369.     /* keep going */
  370.     if((ptr = strpbrk(ptr, findToken->tokens)) != NULL)
  371.     {
  372.         *ptr = NUL;
  373.         findToken->nextStart = ptr;
  374.     }
  375.     else
  376.     {
  377.         findToken->nextStart = findToken->lastChar;
  378.     }
  379.  
  380.     return start;
  381. }
  382.  
  383. /*
  384. // Wrapper function to (a) allocate memory for the duplicated string and
  385. // (b) copy the source string into the new memory location.  Caller is
  386. // responsible to free the string eventually.
  387. */
  388. char *DuplicateString(const char *src)
  389. {
  390.     char *new;
  391.     uint size;
  392.  
  393.     assert(src != NULL);
  394.  
  395.     size = strlen(src) + 1;
  396.  
  397.     /* allocate memory for the duplicate string */
  398.     if((new = AllocMemory(size)) == NULL)
  399.     {
  400.         return NULL;
  401.     }
  402.  
  403.     /* copy the string */
  404.     return memcpy(new, src, size);
  405. }
  406.  
  407. /*
  408. // returns the full, qualified pathname of the default htp include file
  409. //
  410. // Returns FALSE if unable to find the file.
  411. */
  412. BOOL HtpDefaultFilename(char *filename, uint size)
  413. {
  414.     char *defFile;
  415.  
  416.     /* get the name of the default file from the HTPDEF environement */
  417.     /* variable */
  418.     if((defFile = getenv("HTPDEF")) == NULL)
  419.     {
  420.         return FALSE;
  421.     }
  422.  
  423.     /* verify that the file exists */
  424.     if(FileExists(defFile) == FALSE)
  425.     {
  426.         return FALSE;
  427.     }
  428.  
  429.     /* copy the filename into the buffer and skeedaddle */
  430.     StringCopy(filename, defFile, size);
  431.  
  432.     return TRUE;
  433. }
  434.  
  435. /*
  436. // compare files modified time/date stamp, as a dependency check ... returns
  437. // TRUE if the dependency does not require an update, FALSE otherwise (which
  438. // could either be a timestamp discrepency, or simply that the resulting file
  439. // does not exist) ... if dependency checking is turned off, this function
  440. // will always return FALSE.
  441. //
  442. // Returns ERROR if dependency file does not exist.
  443. */
  444. BOOL IsTargetUpdated(const char *dependency, const char *target)
  445. {
  446.     struct stat dependStat;
  447.     struct stat targetStat;
  448.     char *dependName;
  449.     char *targetName;
  450.  
  451.     assert(dependency != NULL);
  452.     assert(target != NULL);
  453.  
  454.     /* always update targets? */
  455.     if(DEPEND == FALSE)
  456.     {
  457.         return FALSE;
  458.     }
  459.  
  460.     /* convert the dependency and target filenames for this filesystem */
  461.     if((dependName = ConvertDirDelimiter(dependency)) == NULL)
  462.     {
  463.         return ERROR;
  464.     }
  465.  
  466.     if((targetName = ConvertDirDelimiter(target)) == NULL)
  467.     {
  468.         FreeMemory(dependName);
  469.         return ERROR;
  470.     }
  471.  
  472.     /* get information on the dependency file */
  473.     if(stat(dependName, &dependStat) != 0)
  474.     {
  475.         /* dependency file needs to exist */
  476.         FreeMemory(dependName);
  477.         FreeMemory(targetName);
  478.  
  479.         return ERROR;
  480.     }
  481.  
  482.     /* get information on the target file */
  483.     if(stat(targetName, &targetStat) != 0)
  484.     {
  485.         /* target file does not exist, dependency needs to be updated */
  486.         FreeMemory(dependName);
  487.         FreeMemory(targetName);
  488.  
  489.         return FALSE;
  490.     }
  491.  
  492.     FreeMemory(dependName);
  493.     FreeMemory(targetName);
  494.  
  495.     /* compare modification times to determine if up-to-date */
  496.     return (dependStat.st_mtime <= targetStat.st_mtime) ? TRUE : FALSE;
  497. }
  498.  
  499. /*
  500. // converts directory delimiters for any pathname into one supporting the
  501. // delimiters used by the present filesystem ... it is encumbent on the
  502. // caller to free() the string returned once finished
  503. */
  504. char *ConvertDirDelimiter(const char *pathname)
  505. {
  506.     char *newPathname;
  507.     char *strptr;
  508.  
  509.     if(pathname == NULL)
  510.     {
  511.         return NULL;
  512.     }
  513.  
  514.     /* duplicate the pathname for conversion */
  515.     if((newPathname = DuplicateString(pathname)) == NULL)
  516.     {
  517.         return NULL;
  518.     }
  519.  
  520.     /* walk the string, looking for delimiters belonging to other filesystems */
  521.     /* replace with native filesystems delimiter */
  522.     strptr = newPathname;
  523.     while(*strptr != NUL)
  524.     {
  525.         if(strchr(OTHER_FILESYSTEM_DELIMITER, *strptr) != NULL)
  526.         {
  527.             *strptr = DIR_DELIMITER;
  528.         }
  529.         strptr++;
  530.     }
  531.  
  532.     return newPathname;
  533. }
  534.  
  535. /*
  536. // uses stat() to check for file existance ... maybe not the best way?
  537. */
  538. BOOL FileExists(const char *pathname)
  539. {
  540.     struct stat dummy;
  541.  
  542.     return (stat(pathname, &dummy) == 0) ? TRUE : FALSE;
  543. }
  544.  
  545. /*
  546. // searches for the specified file in the search path ... this function is
  547. // very stupid, it simply gets the first directory in the search string,
  548. // appends the file directly to the end, and tests for existance.  Repeat.
  549. */
  550. BOOL SearchForFile(const char *filename, char *fullPathname, uint size)
  551. {
  552.     char *searchPathCopy;
  553.     char *ptr;
  554.     char *convertedName;
  555.     FIND_TOKEN findToken;
  556.  
  557.     /* quick check for search path even being defined */
  558.     if(searchPath[0] == NUL)
  559.     {
  560.         return FALSE;
  561.     }
  562.  
  563.     /* need to make a copy of the search path for String...Token() to butcher up */
  564.     if((searchPathCopy = DuplicateString(searchPath)) == NULL)
  565.     {
  566.         printf("%s: unable to allocate temporary buffer for include path (out of memory?)\n",
  567.             PROGRAM_NAME);
  568.         return FALSE;
  569.     }
  570.  
  571.     /* look for ';' delimiter */
  572.     ptr = StringFirstToken(&findToken, searchPathCopy, ";");
  573.     while(ptr != NULL)
  574.     {
  575.         StringCopy(fullPathname, ptr, size);
  576.  
  577.         /* if the last character is not a directory delimiter, add it */
  578.         if(strchr(ALL_FILESYSTEM_DELIMITERS, fullPathname[strlen(fullPathname) - 1]) == NULL)
  579.         {
  580.             strncat(fullPathname, DIR_DELIMITER_STRING, size);
  581.         }
  582.  
  583.         /* append the file name */
  584.         strncat(fullPathname, filename, size);
  585.  
  586.         /* need to do a complete conversion of delimiters in the filename, but */
  587.         /* ConvertDirDelimiter() returns a AllocMemory()'d copy of the string ... */
  588.         convertedName = ConvertDirDelimiter(fullPathname);
  589.  
  590.         /* check for existance */
  591.         if(FileExists(convertedName) == TRUE)
  592.         {
  593.             /* clean up and get outta here */
  594.             StringCopy(fullPathname, convertedName, size);
  595.  
  596.             FreeMemory(searchPathCopy);
  597.             FreeMemory(convertedName);
  598.  
  599.             return TRUE;
  600.         }
  601.  
  602.         FreeMemory(convertedName);
  603.         convertedName = NULL;
  604.  
  605.         ptr = StringNextToken(&findToken);
  606.     }
  607.  
  608.     /* clean up */
  609.     FreeMemory(searchPathCopy);
  610.  
  611.     return FALSE;
  612. }
  613.  
  614. /*
  615. // HTML file stream functions
  616. */
  617.  
  618. /*
  619. // returns the markup type flag ... either the closing or opening delimiter
  620. // in the markup can be passed in
  621. */
  622. uint MarkupType(char delim)
  623. {
  624.     uint markupType;
  625.  
  626.     markupType = 0;
  627.  
  628.     if((delim == HTML_OPEN_MARKUP) || (delim == HTML_CLOSE_MARKUP))
  629.     {
  630.         markupType |= MARKUP_TYPE_HTML;
  631.     }
  632.  
  633.     if((delim == htpOpenMarkup) || (delim == htpCloseMarkup))
  634.     {
  635.         markupType |= MARKUP_TYPE_HTP;
  636.     }
  637.  
  638.     return markupType;
  639. }
  640.  
  641. /*
  642. // TRUE = plaintext is filled with new plain text markup, FALSE if end of file,
  643. // ERROR if a problem
  644. // !! Don't like using ERROR in any BOOL return values
  645. */
  646. BOOL ReadHtmlFile(TEXTFILE *infile, TEXTFILE *outfile, char **plaintext,
  647.     uint *markupType)
  648. {
  649.     char ch;
  650.     uint ctr;
  651.     char *buffer;
  652.     char *newbuffer;
  653.     uint size;
  654.     uint bytesReadPrevLine;
  655.     BOOL inQuotes;
  656.     uint startLine;
  657.  
  658.     assert(infile != NULL);
  659.     assert(plaintext != NULL);
  660.  
  661.     /* if outfile is NULL, then the input stream is just being walked and */
  662.     /* not parsed for output ... i.e., don't assert outfile != NULL */
  663.  
  664.     /* allocate some space for markup plaintext ... this will dynamically */
  665.     /* expand if necessary, and has to be freed by the caller */
  666.     if((buffer = AllocMemory(MIN_PLAINTEXT_SIZE)) == NULL)
  667.     {
  668.         HtpMsg(MSG_ERROR, NULL, "unable to allocate memory to read HTML file");
  669.         return ERROR;
  670.     }
  671.  
  672.     /* track the buffer size */
  673.     size = MIN_PLAINTEXT_SIZE;
  674.  
  675.     for(;;)
  676.     {
  677.         /* GetFileChar() will reset bytesReadThisLine if a EOL char is read */
  678.         /* so save it for later evaluation ... (bytesReadPrevLine is named */
  679.         /* to indicate it is only evaluated if a EOL char is read) */
  680.         bytesReadPrevLine = infile->bytesReadThisLine;
  681.         if(GetFileChar(infile, &ch) == FALSE)
  682.         {
  683.             break;
  684.         }
  685.  
  686.         if(IS_OPEN_MARKUP(ch) == FALSE)
  687.         {
  688.             /* normal text, just copy it to the output file (if there is one) */
  689.             if(outfile != NULL)
  690.             {
  691.                 /* this is used to catch spurious EOLs sneaking into the final output */
  692.                 /* essentially, if bytes were read but none were written, it */
  693.                 /* is assumed that the only text on the line were htp directives */
  694.                 /* and therefore it is unnecessary (and ugly) to write out the */
  695.                 /* EOL at the end of the line */
  696.                 if(ch == '\n')
  697.                 {
  698.                     if((bytesReadPrevLine > 0) && (outfile->bytesWrittenThisLine == 0))
  699.                     {
  700.                         continue;
  701.                     }
  702.                 }
  703.  
  704.                 PutFileChar(outfile, ch);
  705.             }
  706.         }
  707.         else
  708.         {
  709.             /* get the type of markup for caller */
  710.             *markupType = MarkupType(ch);
  711.  
  712.             /* copy the markup into the buffer */
  713.             ctr = 0;
  714.             inQuotes = FALSE;
  715.             startLine = infile->lineNumber;
  716.             for(;;)
  717.             {
  718.                 if(GetFileChar(infile, &ch) == FALSE)
  719.                 {
  720.                     /* EOF ... this is not acceptable before the markup is */
  721.                     /* terminated */
  722.                     FreeMemory(buffer);
  723.                     HtpMsg(MSG_ERROR, infile, "EOF encountered inside markup tag (started on line %u)",
  724.                         startLine);
  725.  
  726.                     return ERROR;
  727.                 }
  728.  
  729.                 if((IS_CLOSE_MARKUP(ch))
  730.                     && (inQuotes == FALSE)
  731.                     && (MarkupType(ch) == *markupType))
  732.                 {
  733.                     /* end of markup, terminate string and exit */
  734.                     buffer[ctr] = NUL;
  735.                     break;
  736.                 }
  737.  
  738.                 /* track quotation marks ... can only close markup when */
  739.                 /* all quotes have been closed */
  740.                 if(ch == '\"')
  741.                 {
  742.                     inQuotes = (inQuotes == TRUE) ? FALSE : TRUE;
  743.                 }
  744.  
  745.                 /* copy it into the plaintext buffer */
  746.                 buffer[ctr++] = ch;
  747.  
  748.                 /* check for overflow ... resize buffer if necessary */
  749.                 if(ctr == size)
  750.                 {
  751.                     newbuffer = ResizeMemory(buffer, size + PLAINTEXT_GROW_SIZE);
  752.                     if(newbuffer == NULL)
  753.                     {
  754.                         /* unable to enlarge buffer area */
  755.                         HtpMsg(MSG_ERROR, NULL, "unable to reallocate memory for reading HTML file");
  756.                         FreeMemory(buffer);
  757.                         return ERROR;
  758.                     }
  759.  
  760.                     buffer = newbuffer;
  761.                     size += PLAINTEXT_GROW_SIZE;
  762.                 }
  763.             }
  764.  
  765.             /* give the buffer pointer to the caller */
  766.             *plaintext = buffer;
  767.  
  768.             return TRUE;
  769.         }
  770.     }
  771.  
  772.     /* no markup found, end of file */
  773.     FreeMemory(buffer);
  774.  
  775.     return FALSE;
  776. }   
  777.  
  778. /*
  779. // specialized markup processors
  780. */
  781.  
  782. uint ImageProcessor(TASK *task, HTML_MARKUP *htmlMarkup, char **newPlaintext)
  783. {
  784.     const char *imgfilename;
  785.     char str[32];
  786.     IMAGEFILE imageFile;
  787.     WORD width, height;
  788.     char *imgSource;
  789.     const char *originalSource;
  790.     char *imgFilename;
  791.     const char *imgText;
  792.  
  793.     UNREF_PARAM(newPlaintext);
  794.  
  795.     /* try to find ALT text in store */
  796.     /* first: is there already ALT attribute?  if so, skip this step */
  797.     if(IsAttributeInMarkup(htmlMarkup, "ALT") == FALSE)
  798.     {
  799.         /* parse down the image source to just the filename */
  800.         originalSource = MarkupAttributeValue(htmlMarkup, "SRC");
  801.         if(originalSource == NULL)
  802.         {
  803.             HtpMsg(MSG_WARNING, task->infile, "image SRC not specified, skipping");
  804.             return MARKUP_OKAY;
  805.         }
  806.  
  807.         imgSource = DuplicateString(originalSource);
  808.         if(imgSource == NULL)
  809.         {
  810.             HtpMsg(MSG_ERROR, task->infile, "out of memory processing IMG tag");
  811.             return MARKUP_ERROR;
  812.         }
  813.  
  814.         /* parse the image name, just find the filename */
  815.         imgFilename = FindFilename(imgSource);
  816.  
  817.         /* is the image in the ALT text store? */
  818.         if(VariableExists(&altTextVarStore, imgFilename) == TRUE)
  819.         {
  820.             /* add the specified text to the image */
  821.             imgText = GetVariableValue(&altTextVarStore, imgFilename);
  822.             assert(imgText != NULL);
  823.             AddAttributeToMarkup(htmlMarkup, "ALT", imgText, TRUE);
  824.             HtpMsg(MSG_INFO, task->infile, "ALT text \"%s\" added to IMG \"%s\"",
  825.                 imgText, imgSource);
  826.         }
  827.  
  828.         FreeMemory(imgSource);
  829.         imgSource = NULL;
  830.     }
  831.  
  832.     /* if option is turned off, then just include the markup as-is */
  833.     if(IMGXY == FALSE)
  834.     {
  835.         return MARKUP_OKAY;
  836.     }
  837.  
  838.     /* if width and/or height are already specified, then include the */
  839.     /* markup as-is with no modifications */
  840.     if(IsAttributeInMarkup(htmlMarkup, "HEIGHT")
  841.         || IsAttributeInMarkup(htmlMarkup, "WIDTH"))
  842.     {
  843.         return MARKUP_OKAY;
  844.     }
  845.  
  846.     /* get the filename of the image */
  847.     if((imgfilename = MarkupAttributeValue(htmlMarkup, "SRC")) == NULL)
  848.     {
  849.         HtpMsg(MSG_ERROR, task->infile, "unable to retrieve image source name");
  850.         return MARKUP_ERROR;
  851.     }
  852.  
  853.     /* open the image file, get its dimensions, and close the file */
  854.     if(OpenImageFile(imgfilename, &imageFile) == FALSE)
  855.     {
  856.         HtpMsg(MSG_WARNING, task->infile, "unable to open image file \"%s\"",
  857.             imgfilename);
  858.         return MARKUP_OKAY;
  859.     }
  860.  
  861.     if(GetImageDimensions(&imageFile, &width, &height) == FALSE)
  862.     {
  863.         HtpMsg(MSG_WARNING, task->infile, "unable to determine image file \"%s\" dimensions",
  864.             imgfilename);
  865.         CloseImageFile(&imageFile);
  866.         return MARKUP_OKAY;
  867.     }
  868.  
  869.     CloseImageFile(&imageFile);
  870.  
  871.     /* add the width and height specifier for the image */
  872.     sprintf(str, "%u", width);
  873.     AddAttributeToMarkup(htmlMarkup, "WIDTH", str, TRUE);
  874.     sprintf(str, "%u", height);
  875.     AddAttributeToMarkup(htmlMarkup, "HEIGHT", str, TRUE);
  876.  
  877.     /* print out an informational message to the user */
  878.     HtpMsg(MSG_INFO, task->outfile, "image file \"%s\" dimensions (%u x %u) added",
  879.         imgfilename, width, height);
  880.  
  881.     /* include the markup in the final output */
  882.     return MARKUP_OKAY;
  883. }   
  884.  
  885. BOOL OptionCallback(const char *name, const char *value, ULONG userParam)
  886. {
  887.     TASK *task;
  888.     BOOL printMsg;
  889.  
  890.     task = (TASK *) userParam;
  891.     printMsg = (task == NULL) ? FALSE : TRUE;
  892.  
  893.     if(name == NULL)
  894.     {
  895.         /* dont like it, but dont stop processing either */
  896.         HtpMsg(MSG_WARNING, task->infile, "unknown option \"%s\" specified", value);
  897.         return TRUE;
  898.     }
  899.  
  900.     if(stricmp(name, OPT_N_QUIET) == 0)
  901.     {
  902.         if(stricmp(value, OPT_V_TRUE) == 0)
  903.         {
  904.             SetMessageSeverityLevel(MSG_WARNING);
  905.         }
  906.         else
  907.         {
  908.             SetMessageSeverityLevel(MSG_INFO);
  909.         }
  910.     }
  911.  
  912.     if(stricmp(name, OPT_N_IMGXY) == 0)
  913.     {
  914.         if(stricmp(value, OPT_V_TRUE) == 0)
  915.         {
  916.             if(printMsg)
  917.             {
  918.                 HtpMsg(MSG_INFO, task->infile, "image pre-processing turned ON");
  919.             }
  920.         }
  921.         else
  922.         {
  923.             if(printMsg)
  924.             {
  925.                 HtpMsg(MSG_INFO, task->infile, "image pre-processing turned OFF");
  926.             }
  927.         }
  928.     }
  929.  
  930.     if(stricmp(name, OPT_N_DEPEND) == 0)
  931.     {
  932.         if(stricmp(value, OPT_V_TRUE) == 0)
  933.         {
  934.             if(printMsg)
  935.             {
  936.                 HtpMsg(MSG_INFO, task->infile, "dependency checking turned ON");
  937.             }
  938.         }
  939.         else
  940.         {
  941.             if(printMsg)
  942.             {
  943.                 HtpMsg(MSG_INFO, task->infile, "dependency checking turned OFF");
  944.             }
  945.         }
  946.     }
  947.  
  948.     if(stricmp(name, OPT_N_PRECIOUS) == 0)
  949.     {
  950.         if(stricmp(value, OPT_V_TRUE) == 0)
  951.         {
  952.             if(printMsg)
  953.             {
  954.                 HtpMsg(MSG_INFO, task->infile, "precious output turned ON");
  955.             }
  956.         }
  957.         else
  958.         {
  959.             if(printMsg)
  960.             {
  961.                 HtpMsg(MSG_INFO, task->infile, "precious output turned OFF");
  962.             }
  963.         }
  964.     }
  965.  
  966.     if(stricmp(name, OPT_N_CONDENSE) == 0)
  967.     {
  968.         if(stricmp(value, OPT_V_TRUE) == 0)
  969.         {
  970.             if(printMsg)
  971.             {
  972.                 HtpMsg(MSG_INFO, task->infile, "output will be condensed");
  973.             }
  974.  
  975.             if(task != NULL)
  976.             {
  977.                 SuppressLinefeeds(task->outfile);
  978.             }
  979.         }
  980.         else
  981.         {
  982.             if(printMsg)
  983.             {
  984.                 HtpMsg(MSG_INFO, task->infile, "output will not be condensed");
  985.             }
  986.  
  987.             if(task != NULL)
  988.             {
  989.                 AllowLinefeeds(task->outfile);
  990.             }
  991.         }
  992.     }
  993.  
  994.     if(stricmp(name, OPT_N_KEEP_TEMP) == 0)
  995.     {
  996.         if(stricmp(value, OPT_V_TRUE) == 0)
  997.         {
  998.             if(printMsg)
  999.             {
  1000.                 HtpMsg(MSG_INFO, task->infile, "DEBUG: keeping temporary files");
  1001.             }
  1002.         }
  1003.         else
  1004.         {
  1005.             if(printMsg)
  1006.             {
  1007.                 HtpMsg(MSG_INFO, task->infile, "DEBUG: not keeping temporary files");
  1008.             }
  1009.         }
  1010.     }
  1011.  
  1012.     if(stricmp(name, OPT_N_SET_DELIM) == 0)
  1013.     {
  1014.         if(stricmp(value, OPT_V_DELIM_HTML) == 0)
  1015.         {
  1016.             htpOpenMarkup = '<';
  1017.             htpCloseMarkup = '>';
  1018.         }
  1019.         else if(stricmp(value, OPT_V_DELIM_SQUARE) == 0)
  1020.         {
  1021.             htpOpenMarkup = '[';
  1022.             htpCloseMarkup = ']';
  1023.         }
  1024.         else if(stricmp(value, OPT_V_DELIM_CURLY) == 0)
  1025.         {
  1026.             htpOpenMarkup = '{';
  1027.             htpCloseMarkup = '}';
  1028.         }
  1029.  
  1030.         if(printMsg)
  1031.         {
  1032.             HtpMsg(MSG_INFO, task->infile, "markup delimiter set to \"%s\"",
  1033.                     value);
  1034.         }
  1035.     }
  1036.  
  1037.     return TRUE;
  1038. }
  1039.  
  1040. uint OptionProcessor(TASK *task, HTML_MARKUP *htmlMarkup, char **newPlaintext)
  1041. {
  1042.     UNREF_PARAM(newPlaintext);
  1043.  
  1044.     if(ParseMarkupOption(htmlMarkup, OptionCallback, (ULONG) task) == FALSE)
  1045.     {
  1046.         HtpMsg(MSG_ERROR, task->infile, "error parsing markup options (out of memory?)");
  1047.         return MARKUP_ERROR;
  1048.     }
  1049.  
  1050.     return DISCARD_MARKUP;
  1051. }   
  1052.  
  1053. uint ExternalFileProcessor(TASK *task, HTML_MARKUP *htmlMarkup,
  1054.     const char *externalName, char **newPlaintext)
  1055. {
  1056.     struct stat fileStat;
  1057.     struct tm *fileTime;
  1058.     uint precision;
  1059.     const char *attribValue;
  1060.     const char *precisionString;
  1061.     const char *value;
  1062.  
  1063.     assert(externalName != NULL);
  1064.     assert(htmlMarkup != NULL);
  1065.  
  1066.     /* get information on the file itself */
  1067.     if(stat(externalName, &fileStat) != 0)
  1068.     {
  1069.         HtpMsg(MSG_ERROR, task->infile, "unable to retrieve file information on \"%s\"",
  1070.             externalName);
  1071.         return MARKUP_ERROR;
  1072.     }
  1073.  
  1074.     /* get the precision attribute value, if present */
  1075.     /* (this is only valid for SIZE attribute, but not checking for simplicity */
  1076.     /* ignored for other types of FILE attributes) */
  1077.     precision = DEFAULT_PRECISION;
  1078.     if(IsAttributeInMarkup(htmlMarkup, "PRECISION"))
  1079.     {
  1080.         precisionString = MarkupAttributeValue(htmlMarkup, "PRECISION");
  1081.         if(precisionString != NULL)
  1082.         {
  1083.             precision = atoi(precisionString);
  1084.         }
  1085.         else
  1086.         {
  1087.             HtpMsg(MSG_WARNING, task->infile, "precision attribute needs a value");
  1088.         }
  1089.     }
  1090.  
  1091.     /* allocate room for the replacment plaintext */
  1092.     if((*newPlaintext = AllocMemory(MAX_TIME_DATE_SIZE)) == NULL)
  1093.     {
  1094.         HtpMsg(MSG_ERROR, task->infile, "unable to allocate memory for expansion");
  1095.         return MARKUP_ERROR;
  1096.     }
  1097.  
  1098.     /* create new plaintext depending on what extra information is specified */
  1099.     /* !! this is technically not correct ... SIZE, TIME, and DATE are */
  1100.     /* not allowed in the same markup but not checking that only one is */
  1101.     /* present and not any others */
  1102.     if(IsAttributeInMarkup(htmlMarkup, "SIZE"))
  1103.     {
  1104.         attribValue = MarkupAttributeValue(htmlMarkup, "SIZE");
  1105.  
  1106.         /* expand markup depending on how SIZE should be represented */
  1107.         if((attribValue == NULL) || (stricmp(attribValue, "BYTE") == 0))
  1108.         {
  1109.             /* byte representation is default */
  1110.             sprintf(*newPlaintext, "%lu", fileStat.st_size);
  1111.         }
  1112.         else if(stricmp(attribValue, "KBYTE") == 0)
  1113.         {
  1114.             sprintf(*newPlaintext, "%.*f", (int) precision,
  1115.                 (double) ((double) fileStat.st_size / (double) KBYTE));
  1116.         }
  1117.         else if(stricmp(attribValue, "MBYTE") == 0)
  1118.         {
  1119.             sprintf(*newPlaintext, "%.*f", (int) precision,
  1120.                 (double) ((double) fileStat.st_size / (double) MBYTE));
  1121.         }
  1122.         else if(stricmp(attribValue, "GBYTE") == 0)
  1123.         {
  1124.             sprintf(*newPlaintext, "%.*f", (int) precision,
  1125.                 (double) ((double) fileStat.st_size / (double) GBYTE));
  1126.         }
  1127.         else
  1128.         {
  1129.             /* free the plaintext memory before returning */
  1130.             HtpMsg(MSG_ERROR, task->infile, "unknown SIZE specifier");
  1131.             FreeMemory(*newPlaintext);
  1132.             *newPlaintext = NULL;
  1133.             return MARKUP_ERROR;
  1134.         }
  1135.     }
  1136.     else if(IsAttributeInMarkup(htmlMarkup, "TIME"))
  1137.     {
  1138.         const char *value;
  1139.  
  1140.         /* convert into an ANSI time structure */
  1141.         fileTime = localtime(&fileStat.st_mtime);
  1142.  
  1143.         /* see if the attribute has a value ... if so, let it be the */
  1144.         /* strftime() formatter */
  1145.         if((value = MarkupAttributeValue(htmlMarkup, "TIME")) != NULL)
  1146.         {
  1147.             strftime(*newPlaintext, MAX_TIME_DATE_SIZE, value, fileTime);
  1148.         }
  1149.         else
  1150.         {
  1151.             strftime(*newPlaintext, MAX_TIME_DATE_SIZE, "%I:%M:%S %p", fileTime);
  1152.         }
  1153.     }
  1154.     else if(IsAttributeInMarkup(htmlMarkup, "DATE"))
  1155.     {
  1156.         /* convert into an ANSI time structure */
  1157.         fileTime = localtime(&fileStat.st_mtime);
  1158.  
  1159.         /* see if the attribute has a value ... if so, let it be the */
  1160.         /* strftime() formatter */
  1161.         if((value = MarkupAttributeValue(htmlMarkup, "DATE")) != NULL)
  1162.         {
  1163.             strftime(*newPlaintext, MAX_TIME_DATE_SIZE, value, fileTime);
  1164.         }
  1165.         else
  1166.         {
  1167.             strftime(*newPlaintext, MAX_TIME_DATE_SIZE, "%a %b %d, %Y", fileTime);
  1168.         }
  1169.     }
  1170.     else
  1171.     {
  1172.         /* free the plaintext, unused */
  1173.         FreeMemory(*newPlaintext);
  1174.         *newPlaintext = NULL;
  1175.  
  1176.         HtpMsg(MSG_ERROR, task->infile, "bad file information specifier");
  1177.         return MARKUP_ERROR;
  1178.     }
  1179.  
  1180.     /* the new plaintext was created successfully */
  1181.     return NEW_MARKUP;
  1182. }
  1183.  
  1184. uint FileExecuteProcessor(TASK *task, HTML_MARKUP *htmlMarkup)
  1185. {
  1186.     const char *cmdline;
  1187.     const char *output;
  1188.     char newCmdline[MAX_CMDLINE_LEN];
  1189.     char tempFilename[MAX_PATHNAME_LEN];
  1190.     BOOL redirect;
  1191.     TEXTFILE infile;
  1192.     TASK newTask;
  1193.     BOOL result;
  1194.     uint exitCode;
  1195.  
  1196.     assert(task != NULL);
  1197.     assert(htmlMarkup != NULL);
  1198.  
  1199.     if((cmdline = MarkupAttributeValue(htmlMarkup, "EXECUTE")) == NULL)
  1200.     {
  1201.         HtpMsg(MSG_ERROR, task->infile,"FILE EXECUTE must specify a command-line");
  1202.         return MARKUP_ERROR;
  1203.     }
  1204.  
  1205.     output = MarkupAttributeValue(htmlMarkup, "OUTPUT");
  1206.     redirect = IsAttributeInMarkup(htmlMarkup, "REDIRECT");
  1207.  
  1208.     /* either output or redirect, but not both, must be specified */
  1209.     if((output == NULL) && (redirect == FALSE))
  1210.     {
  1211.         HtpMsg(MSG_ERROR, task->infile, "Either REDIRECT or OUTPUT must be specified for FILE EXECUTE");
  1212.         return MARKUP_ERROR;
  1213.     }
  1214.  
  1215.     if((output != NULL) && (redirect == TRUE))
  1216.     {
  1217.         HtpMsg(MSG_ERROR, task->infile, "REDIRECT and OUTPUT cannot both be specified for FILE EXECUTE");
  1218.         return MARKUP_ERROR;
  1219.     }
  1220.  
  1221.     StringCopy(newCmdline, cmdline, MAX_CMDLINE_LEN);
  1222.  
  1223.     /* if redirection required, append to the command-line a redirector to */
  1224.     /* a temporary file */
  1225.     if(redirect)
  1226.     {
  1227.         if(CreateTempFilename(tempFilename, MAX_PATHNAME_LEN) == FALSE)
  1228.         {
  1229.             HtpMsg(MSG_ERROR, task->infile, "unable to create a temporary file for redirection");
  1230.             return MARKUP_ERROR;
  1231.         }
  1232.  
  1233.         strncat(newCmdline, " > ", MAX_PATHNAME_LEN);
  1234.         strncat(newCmdline, tempFilename, MAX_PATHNAME_LEN);
  1235.     }
  1236.     else
  1237.     {
  1238.         /* the specified output file is the "temporary" filename */
  1239.         StringCopy(tempFilename, output, MAX_PATHNAME_LEN);
  1240.     }
  1241.  
  1242.     HtpMsg(MSG_INFO, task->infile, "Executing command \"%s\" ...", newCmdline);
  1243.  
  1244.     /* execute the command */
  1245.     exitCode = system(newCmdline);
  1246.  
  1247.     if(exitCode != 0)
  1248.     {
  1249.         if(IsAttributeInMarkup(htmlMarkup, "NOERROR") == FALSE)
  1250.         {
  1251.             /* the program has exited with an error condition */
  1252.             HtpMsg(MSG_ERROR, task->infile, "Command \"%s\" exited with an error code of %u",
  1253.                 newCmdline, exitCode);
  1254.  
  1255.             /* remove the temporary file, in case it was partially created */
  1256.             remove(tempFilename);
  1257.  
  1258.             return MARKUP_ERROR;
  1259.         }
  1260.     }
  1261.  
  1262.     /* include the output file like it was anything else */
  1263.     /* first, open it */
  1264.     if(OpenFile(tempFilename, tempFilename, "r", &infile) == FALSE)
  1265.     {
  1266.         HtpMsg(MSG_ERROR, task->infile, "unable to open execute result file");
  1267.         return MARKUP_ERROR;
  1268.     }
  1269.  
  1270.     /* build a new task */
  1271.     newTask.infile = &infile;
  1272.     newTask.outfile = task->outfile;
  1273.     newTask.varstore = task->varstore;
  1274.     newTask.sourceFilename = task->sourceFilename;
  1275.  
  1276.     /* process the file */
  1277.     result = ProcessTask(&newTask);
  1278.  
  1279.     /* close and destroy the output file */
  1280.     CloseFile(&infile);
  1281.     remove(tempFilename);
  1282.  
  1283.     return (result == TRUE) ? DISCARD_MARKUP : MARKUP_ERROR;
  1284. }
  1285.  
  1286. uint FileTemplateProcessor(TASK *task, HTML_MARKUP *htmlMarkup)
  1287. {
  1288.     const char *templateFile;
  1289.  
  1290.     assert(task != NULL);
  1291.     assert(htmlMarkup != NULL);
  1292.  
  1293.     if((templateFile = MarkupAttributeValue(htmlMarkup, "TEMPLATE")) == NULL)
  1294.     {
  1295.         HtpMsg(MSG_ERROR, task->infile, "a template file must be specified");
  1296.         return MARKUP_ERROR;
  1297.     }
  1298.  
  1299.     /* the template file is not actually processed now, but rather when the */
  1300.     /* rest of the file is completed ... to postpone processing, the template */
  1301.     /* name is kept in the variable store under a special name and retrieved */
  1302.     /* later */
  1303.     if(StoreVariable(task->varstore, VAR_TEMPLATE_NAME, templateFile,
  1304.         VAR_TYPE_INTERNAL, 0, NULL, NULL) == FALSE)
  1305.     {
  1306.         HtpMsg(MSG_ERROR, task->infile,
  1307.             "unable to store template filename for post-processing (out of memory?)");
  1308.         return MARKUP_ERROR;
  1309.     }
  1310.  
  1311.     return DISCARD_MARKUP;
  1312. }
  1313.  
  1314. uint FileIncludeProcessor(TASK *task, HTML_MARKUP *htmlMarkup)
  1315. {
  1316.     TEXTFILE incfile;
  1317.     BOOL result;
  1318.     TASK newTask;
  1319.     char fullPathname[MAX_PATHNAME_LEN];
  1320.     const char *attribValue;
  1321.     VARSTORE varstore;
  1322.     VARSTORE *topVarstore;
  1323.     uint ctr;
  1324.     uint flag;
  1325.  
  1326.     /* get the filename to include */
  1327.     attribValue = MarkupAttributeValue(htmlMarkup, "INCLUDE");
  1328.     if(attribValue == NULL)
  1329.     {
  1330.         HtpMsg(MSG_ERROR, task->infile, "include filename not specified");
  1331.         return MARKUP_ERROR;
  1332.     }
  1333.  
  1334.     /* open the include file as input */
  1335.     if(OpenFile(attribValue, attribValue, "r", &incfile) == FALSE)
  1336.     {
  1337.         /* use the search path to find the file */
  1338.         if(SearchForFile(attribValue, fullPathname, MAX_PATHNAME_LEN) == FALSE)
  1339.         {
  1340.             /* could not find the file in the search path either */
  1341.             HtpMsg(MSG_ERROR, task->infile, "unable to open include file \"%s\"",
  1342.                 attribValue);
  1343.             return MARKUP_ERROR;
  1344.         }
  1345.  
  1346.         if(OpenFile(fullPathname, fullPathname, "r", &incfile) == FALSE)
  1347.         {
  1348.             HtpMsg(MSG_ERROR, task->infile, "unable to open include file \"%s\"",
  1349.                 fullPathname);
  1350.             return MARKUP_ERROR;
  1351.         }
  1352.     }
  1353.     else
  1354.     {
  1355.         StringCopy(fullPathname, attribValue, MAX_PATHNAME_LEN);
  1356.     }
  1357.  
  1358.     /* if additional parameters exist in the tag, build a local varstore */
  1359.     /* and push it onto the context */
  1360.     if(htmlMarkup->attribCount > 1)
  1361.     {
  1362.         if(InitializeVariableStore(&varstore) == FALSE)
  1363.         {
  1364.             HtpMsg(MSG_ERROR, task->infile, "unable to initialize context for include file");
  1365.             CloseFile(&incfile);
  1366.             return MARKUP_ERROR;
  1367.         }
  1368.  
  1369.         /* start including the parameters as local macros */
  1370.         for(ctr = 1; ctr < htmlMarkup->attribCount; ctr++)
  1371.         {
  1372.             flag = (htmlMarkup->attrib[ctr].quoted == TRUE) ? VAR_FLAG_QUOTED
  1373.                 : VAR_FLAG_NONE;
  1374.             if(StoreVariable(&varstore, htmlMarkup->attrib[ctr].name,
  1375.                 htmlMarkup->attrib[ctr].value, VAR_TYPE_SET_MACRO,
  1376.                 flag, NULL, NULL) == FALSE)
  1377.             {
  1378.                 HtpMsg(MSG_ERROR, task->infile, "unable to add variable to context for include file");
  1379.                 CloseFile(&incfile);
  1380.                 DestroyVariableStore(&varstore);
  1381.                 return MARKUP_ERROR;
  1382.             }
  1383.         }
  1384.  
  1385.         /* push this onto the context and use it for the include file */
  1386.         PushVariableStoreContext(task->varstore, &varstore);
  1387.         topVarstore = &varstore;
  1388.     }
  1389.     else
  1390.     {
  1391.         topVarstore = task->varstore;
  1392.     }
  1393.  
  1394.     /* build a new task structure */
  1395.     newTask.infile = &incfile;
  1396.     newTask.outfile = task->outfile;
  1397.     newTask.varstore = topVarstore;
  1398.     newTask.sourceFilename = task->sourceFilename;
  1399.  
  1400.     /* informational message for the user */
  1401.     HtpMsg(MSG_INFO, task->infile, "including file \"%s\"", fullPathname);
  1402.  
  1403.     /* process the new input file */
  1404.     result = ProcessTask(&newTask);
  1405.  
  1406.     /* pop the local context */
  1407.     if(topVarstore == &varstore)
  1408.     {
  1409.         assert(PeekVariableStoreContext(topVarstore) == topVarstore);
  1410.         PopVariableStoreContext(topVarstore);
  1411.         DestroyVariableStore(&varstore);
  1412.     }
  1413.  
  1414.     CloseFile(&incfile);
  1415.  
  1416.     /* if the new file did not process, return an error, otherwise discard */
  1417.     /* the markup */
  1418.     return (result == TRUE) ? DISCARD_MARKUP : MARKUP_ERROR;
  1419. }
  1420.  
  1421. uint FileProcessor(TASK *task, HTML_MARKUP *htmlMarkup, char **newPlaintext)
  1422. {
  1423.     const char *attribName;
  1424.     const char *attribValue;
  1425.     const char *externalName;
  1426.     struct tm *timeNow;
  1427.     struct stat fileStat;
  1428.  
  1429.     /* if a NAME attribute is found, and it contains a value, use the */
  1430.     /* ExternalFileProcessor to create the plaintext (this function only */
  1431.     /* reports output file's time, date, name) */
  1432.     if(IsAttributeInMarkup(htmlMarkup, "NAME"))
  1433.     {
  1434.         if((externalName = MarkupAttributeValue(htmlMarkup, "NAME")) != NULL)
  1435.         {
  1436.             return ExternalFileProcessor(task, htmlMarkup, externalName,
  1437.                 newPlaintext);
  1438.         }
  1439.     }
  1440.  
  1441.     /* if NAME attribute not in markup, or no external filename specified, */
  1442.     /* only one attribute can be used: NAME, SIZE, TIME, DATE , or INCLUDE */
  1443.     /* (the exception being EXECUTE and TEMPLATE) */
  1444.     if(IsAttributeInMarkup(htmlMarkup, "EXECUTE") == TRUE)
  1445.     {
  1446.         return FileExecuteProcessor(task, htmlMarkup);
  1447.     }
  1448.  
  1449.     if(IsAttributeInMarkup(htmlMarkup, "TEMPLATE") == TRUE)
  1450.     {
  1451.         return FileTemplateProcessor(task, htmlMarkup);
  1452.     }
  1453.  
  1454.     if(IsAttributeInMarkup(htmlMarkup, "INCLUDE") == TRUE)
  1455.     {
  1456.         return FileIncludeProcessor(task, htmlMarkup);
  1457.     }
  1458.  
  1459.     if(htmlMarkup->attribCount != 1)
  1460.     {
  1461.         HtpMsg(MSG_ERROR, task->infile, "improper FILE syntax");
  1462.         return MARKUP_ERROR;
  1463.     }
  1464.  
  1465.     /* get the attribute */
  1466.     attribName = htmlMarkup->attrib[0].name;
  1467.     attribValue = htmlMarkup->attrib[0].value;
  1468.  
  1469.     /* act on the attribute */
  1470.     if(stricmp(attribName, "SEARCH") == 0)
  1471.     {
  1472.         /* set the include search path to what was specified */
  1473.         if(attribValue != NULL)
  1474.         {
  1475.             StringCopy(searchPath, attribValue, SEARCH_PATH_SIZE);
  1476.         }
  1477.         else
  1478.         {
  1479.             /* search path is cleared */
  1480.             searchPath[0] = NUL;
  1481.         }
  1482.  
  1483.         return DISCARD_MARKUP;
  1484.     }
  1485.     else
  1486.     {
  1487.         /* NAME, TIME, DATE or bad tag */
  1488.         /* first, allocate some space for the (possibly) new markup */
  1489.         if((*newPlaintext = AllocMemory(MAX_TIME_DATE_SIZE)) == NULL)
  1490.         {
  1491.             HtpMsg(MSG_ERROR, task->infile, "unable to allocate memory for expansion");
  1492.             return MARKUP_ERROR;
  1493.         }
  1494.  
  1495.         /* get the input files time, in case the attribute is TIME or DATE */
  1496.         if(stat(task->sourceFilename, &fileStat) != 0)
  1497.         {
  1498.             HtpMsg(MSG_ERROR, task->infile, "unable to get information for file \"%s\"\n",
  1499.                 task->infile->filename);
  1500.             return MARKUP_ERROR;
  1501.         }
  1502.  
  1503.         timeNow = localtime(&fileStat.st_mtime);
  1504.  
  1505.         if(stricmp(attribName, "TIME") == 0)
  1506.         {
  1507.             if(attribValue != NULL)
  1508.             {
  1509.                 strftime(*newPlaintext, MAX_TIME_DATE_SIZE, attribValue, timeNow);
  1510.             }
  1511.             else
  1512.             {
  1513.                 strftime(*newPlaintext, MAX_TIME_DATE_SIZE, "%I:%M:%S %p", timeNow);
  1514.             }
  1515.  
  1516.             HtpMsg(MSG_INFO, task->outfile, "adding local time");
  1517.         }
  1518.         else if(stricmp(attribName, "DATE") == 0)
  1519.         {
  1520.             if(attribValue != NULL)
  1521.             {
  1522.                 strftime(*newPlaintext, MAX_TIME_DATE_SIZE, attribValue, timeNow);
  1523.             }
  1524.             else
  1525.             {
  1526.                 strftime(*newPlaintext, MAX_TIME_DATE_SIZE, "%a %b %d, %Y", timeNow);
  1527.             }
  1528.  
  1529.             HtpMsg(MSG_INFO, task->outfile, "adding local date");
  1530.         }
  1531.         else if(stricmp(attribName, "NAME") == 0)
  1532.         {
  1533.             StringCopy(*newPlaintext, task->outfile->name, MAX_TIME_DATE_SIZE);
  1534.  
  1535.             HtpMsg(MSG_INFO, task->outfile, "adding output filename");
  1536.         }
  1537.         else
  1538.         {
  1539.             /* no appropriate tags found */
  1540.             HtpMsg(MSG_ERROR, task->infile, "invalid FILE tag attribute \"%s\"",
  1541.                 attribName);
  1542.  
  1543.             /* free the allocated plaintext buffer */
  1544.             FreeMemory(*newPlaintext);
  1545.             *newPlaintext = NULL;
  1546.  
  1547.             return MARKUP_ERROR;
  1548.         }
  1549.     }
  1550.  
  1551.     /* the new plaintext has been created */
  1552.     return NEW_MARKUP;
  1553. }   
  1554.  
  1555. uint SetProcessor(TASK *task, HTML_MARKUP *htmlMarkup, char **newPlaintext)
  1556. {
  1557.     char *name;
  1558.     char *value;
  1559.     uint ctr;
  1560.     uint flag;
  1561.     HTML_ATTRIBUTE *attrib;
  1562.  
  1563.     UNREF_PARAM(newPlaintext);
  1564.  
  1565.     /* have to declare at least one macro, but more are acceptable */
  1566.     if(htmlMarkup->attribCount == 0)
  1567.     {
  1568.         HtpMsg(MSG_ERROR, task->infile, "incomplete macro declaration");
  1569.         return MARKUP_ERROR;
  1570.     }
  1571.  
  1572.     attrib = &htmlMarkup->attrib[0];
  1573.  
  1574.     /* walk the list and add each macro to the variable store */
  1575.     for(ctr = 0; ctr < htmlMarkup->attribCount; ctr++)
  1576.     {
  1577.         /* get a private copy of the macro name */
  1578.         if((name = DuplicateString(attrib->name)) == NULL)
  1579.         {
  1580.             HtpMsg(MSG_ERROR, task->infile, "unable to store macro's value (out of memory?)");
  1581.             return MARKUP_ERROR;
  1582.         }
  1583.  
  1584.         value = attrib->value;
  1585.         flag = (attrib->quoted) ? VAR_FLAG_QUOTED : VAR_FLAG_NONE;
  1586.  
  1587.         /* put the new variable into the store */
  1588.         if(StoreVariable(task->varstore, name, value, VAR_TYPE_SET_MACRO, flag,
  1589.             NULL, NULL) == FALSE)
  1590.         {
  1591.             HtpMsg(MSG_ERROR, task->infile, "unable to store macro \"%s\" (out of memory?)",
  1592.                 name);
  1593.             FreeMemory(name);
  1594.             return MARKUP_ERROR;
  1595.         }
  1596.  
  1597.         if(value != NULL)
  1598.         {
  1599.             HtpMsg(MSG_INFO, task->infile, "macro \"%s\" assigned value \"%s\"",
  1600.                 name, value);
  1601.         }
  1602.         else
  1603.         {
  1604.             HtpMsg(MSG_INFO, task->infile, "macro \"%s\" created with null value",
  1605.                 name);
  1606.         }
  1607.  
  1608.         FreeMemory(name);
  1609.  
  1610.         attrib++;
  1611.     }
  1612.  
  1613.     return DISCARD_MARKUP;
  1614. }   
  1615.  
  1616. uint UnsetProcessor(TASK *task, HTML_MARKUP *htmlMarkup, char **newPlaintext)
  1617. {
  1618.     uint ctr;
  1619.     uint type;
  1620.     const char *name;
  1621.  
  1622.     UNREF_PARAM(newPlaintext);
  1623.  
  1624.     /* have to specify at least one macro to remove, but more are acceptable */
  1625.     if(htmlMarkup->attribCount == 0)
  1626.     {
  1627.         HtpMsg(MSG_ERROR, task->infile, "UNSET tag improperly specified");
  1628.     }
  1629.  
  1630.     /* walk the attributes list */
  1631.     for(ctr = 0; ctr < htmlMarkup->attribCount; ctr++)
  1632.     {
  1633.         name = htmlMarkup->attrib[ctr].name;
  1634.  
  1635.         /* verify that the variable exists */
  1636.         if(VariableExists(task->varstore, name) == FALSE)
  1637.         {
  1638.             HtpMsg(MSG_ERROR, task->infile, "bad macro name \"%s\"", name);
  1639.             return MARKUP_ERROR;
  1640.         }
  1641.  
  1642.         /* remove it from the variable store if its not a DEF macro */
  1643.         type = GetVariableType(task->varstore, name);
  1644.  
  1645.         if((type == VAR_TYPE_SET_MACRO) || (type == VAR_TYPE_BLOCK_MACRO))
  1646.         {
  1647.             RemoveVariable(task->varstore, name);
  1648.         }
  1649.  
  1650.         HtpMsg(MSG_INFO, task->infile, "variable \"%s\" removed", name);
  1651.     }
  1652.  
  1653.     return DISCARD_MARKUP;
  1654. }
  1655.  
  1656. uint UseProcessor(TASK *task, HTML_MARKUP *htmlMarkup, char **newPlaintext)
  1657. {
  1658.     char *name;
  1659.     const char *value;
  1660.     TEXTFILE incfile;
  1661.     int result;
  1662.     uint type;
  1663.     VARSTORE varstore;
  1664.     VARSTORE *topVarstore;
  1665.     uint ctr;
  1666.     uint flag;
  1667.  
  1668.     /* must declare at least 1 attribute, the macro name */
  1669.     if(htmlMarkup->attribCount == 0)
  1670.     {
  1671.         HtpMsg(MSG_ERROR, task->infile, "macro declaration not complete");
  1672.         return MARKUP_ERROR;
  1673.     }
  1674.  
  1675.     /* a variable reference should NOT include a new declaration */
  1676.     if(htmlMarkup->attrib[0].value != NULL)
  1677.     {
  1678.         HtpMsg(MSG_ERROR, task->infile, "improper USE syntax");
  1679.         return MARKUP_ERROR;
  1680.     }
  1681.  
  1682.     /* get a private copy of the name variable, this function will modify */
  1683.     /* its own copy */
  1684.     if((name = DuplicateString(htmlMarkup->attrib[0].name)) == NULL)
  1685.     {
  1686.         HtpMsg(MSG_ERROR, task->infile, "unable to expand macro (out of memory?)");
  1687.         return MARKUP_ERROR;
  1688.     }
  1689.  
  1690.     /* verify the macro exists */
  1691.     if(VariableExists(task->varstore, name) == FALSE)
  1692.     {
  1693.         HtpMsg(MSG_ERROR, task->infile, "macro %s has not been declared", name);
  1694.         FreeMemory(name);
  1695.         return MARKUP_ERROR;
  1696.     }
  1697.  
  1698.     /* get the value of the macro */
  1699.     value = GetVariableValue(task->varstore, name);
  1700.  
  1701.     /* get the type of macro */
  1702.     type = GetVariableType(task->varstore, name);
  1703.  
  1704.     if(type == VAR_TYPE_INTERNAL)
  1705.     {
  1706.         /* oof ... the user picked a variable name we use internally */
  1707.         /* !! a fix is to use both type and name as a key to get variable */
  1708.         /* out of hash, and therefore the name is not the only identifier */
  1709.         /* this will have to wait for later */
  1710.         HtpMsg(MSG_ERROR, task->infile,
  1711.             "reserved variable name \"%s\" used ... please use different name in HTP file",
  1712.             name);
  1713.         FreeMemory(name);
  1714.         return MARKUP_ERROR;
  1715.     }
  1716.  
  1717.     if(type == VAR_TYPE_DEF_MACRO)
  1718.     {
  1719.         /* nope */
  1720.         HtpMsg(MSG_ERROR, task->infile,
  1721.             "illegal to dereference a DEF macro with USE");
  1722.         FreeMemory(name);
  1723.         return MARKUP_ERROR;
  1724.     }
  1725.  
  1726.     assert((type == VAR_TYPE_SET_MACRO) || (type == VAR_TYPE_BLOCK_MACRO));
  1727.  
  1728.     /* if more than one parameter is on the USE tag, then assume they are */
  1729.     /* local variables for the macro */
  1730.     if(htmlMarkup->attribCount > 1)
  1731.     {
  1732.         if(type == VAR_TYPE_SET_MACRO)
  1733.         {
  1734.             /* nope, not yet */
  1735.             HtpMsg(MSG_ERROR, task->infile, "macro parameters can only be used for BLOCK macros");
  1736.             FreeMemory(name);
  1737.             return MARKUP_ERROR;
  1738.         }
  1739.  
  1740.         /* create a local variable store */
  1741.         if(InitializeVariableStore(&varstore) == FALSE)
  1742.         {
  1743.             HtpMsg(MSG_ERROR, task->infile, "unable to initialize local context for macro");
  1744.             FreeMemory(name);
  1745.             return MARKUP_ERROR;
  1746.         }
  1747.  
  1748.         /* add each additional parameter to the local varstore */
  1749.         for(ctr = 1; ctr < htmlMarkup->attribCount; ctr++)
  1750.         {
  1751.             flag = (htmlMarkup->attrib[ctr].quoted == TRUE) ? VAR_FLAG_QUOTED
  1752.                 : VAR_FLAG_NONE;
  1753.             if(StoreVariable(&varstore, htmlMarkup->attrib[ctr].name,
  1754.                 htmlMarkup->attrib[ctr].value, VAR_TYPE_SET_MACRO, flag,
  1755.                 NULL, NULL) == FALSE)
  1756.             {
  1757.                 HtpMsg(MSG_ERROR, task->infile, "unable to add variable to block's local context");
  1758.                 DestroyVariableStore(&varstore);
  1759.                 FreeMemory(name);
  1760.                 return MARKUP_ERROR;
  1761.             }
  1762.         }
  1763.  
  1764.         /* make this variable store the topmost context */
  1765.         PushVariableStoreContext(task->varstore, &varstore);
  1766.         topVarstore = &varstore;
  1767.     }
  1768.     else
  1769.     {
  1770.         topVarstore = task->varstore;
  1771.     }
  1772.  
  1773.     if(type == VAR_TYPE_SET_MACRO)
  1774.     {
  1775.         /* if NULL, then the macro was declared with no value, this is okay, */
  1776.         /* just don't do anything */
  1777.         if(value == NULL)
  1778.         {
  1779.             FreeMemory(name);
  1780.             return DISCARD_MARKUP;
  1781.         }
  1782.  
  1783.         /* allocate the new plaintext buffer and copy in the macro value */
  1784.         if((*newPlaintext = DuplicateString(value)) == NULL)
  1785.         {
  1786.             HtpMsg(MSG_ERROR, task->infile, "unable to allocate memory for macro expansion");
  1787.             FreeMemory(name);
  1788.             return MARKUP_ERROR;
  1789.         }
  1790.  
  1791.         HtpMsg(MSG_INFO, task->infile, "macro \"%s\" dereferenced", name);
  1792.  
  1793.         FreeMemory(name);
  1794.  
  1795.         return NEW_MARKUP;
  1796.     }
  1797.     else if(type == VAR_TYPE_BLOCK_MACRO)
  1798.     {
  1799.         /* !! magic number */
  1800.         char blockName[128];
  1801.         TASK newTask;
  1802.  
  1803.         /* if NULL, big-time error */
  1804.         if(value == NULL)
  1805.         {
  1806.             HtpMsg(MSG_ERROR, task->infile, "block macro \"%s\" incorrectly stored, fatal internal error",
  1807.                 name);
  1808.             if(topVarstore == &varstore)
  1809.             {
  1810.                 assert(PeekVariableStoreContext(topVarstore) == topVarstore);
  1811.                 PopVariableStoreContext(topVarstore);
  1812.                 DestroyVariableStore(&varstore);
  1813.             }
  1814.             FreeMemory(name);
  1815.             exit(1);
  1816.         }
  1817.  
  1818.         /* build the block macro name (printed in place of the temporary */
  1819.         /* filename in all messages regarding it) */
  1820.         sprintf(blockName, "Block macro \"%s\"", name);
  1821.  
  1822.         /* block macro value is the name of a temporary file containing */
  1823.         /* macro contents */
  1824.         if(OpenFile(blockName, value, "r", &incfile) == FALSE)
  1825.         {
  1826.             HtpMsg(MSG_ERROR, task->infile,
  1827.                 "unable to open temporary file \"%s\" for block macro \"%s\"",
  1828.                 value, name);
  1829.             FreeMemory(name);
  1830.             return MARKUP_ERROR;
  1831.         }
  1832.  
  1833.         HtpMsg(MSG_INFO, task->infile, "dereferencing block macro \"%s\"", name);
  1834.  
  1835.         /* build a new task structure */
  1836.         newTask.infile = &incfile;
  1837.         newTask.outfile = task->outfile;
  1838.         newTask.sourceFilename = task->sourceFilename;
  1839.  
  1840.         /* re-use current variable store if no local variable store was */
  1841.         /* allocated, otherwise use the new one */
  1842.         newTask.varstore = topVarstore;
  1843.  
  1844.         /* process the new input file */
  1845.         result = ProcessTask(&newTask);
  1846.  
  1847.         /* remove the new context (and make sure it is, in fact, the block's */
  1848.         /* context) */
  1849.         if(topVarstore == &varstore)
  1850.         {
  1851.             assert(PeekVariableStoreContext(topVarstore) == topVarstore);
  1852.             PopVariableStoreContext(topVarstore);
  1853.             DestroyVariableStore(&varstore);
  1854.         }
  1855.  
  1856.         CloseFile(&incfile);
  1857.  
  1858.         FreeMemory(name);
  1859.  
  1860.         /* if the new file did not process, return an error, otherwise discard */
  1861.         /* the markup */
  1862.         return (result == TRUE) ? DISCARD_MARKUP : MARKUP_ERROR;
  1863.     }
  1864.     else
  1865.     {
  1866.         /* fatal error */
  1867.         printf("%s: fatal internal error (USE)\n", PROGRAM_NAME);
  1868.         FreeMemory(name);
  1869.         exit(1);
  1870.  
  1871.         /* to prevent compiler warning */
  1872.         return MARKUP_ERROR;
  1873.     }
  1874. }   
  1875.  
  1876. /*
  1877. // Block macro destructor callback ... used whenever a block macro is
  1878. // destroyed with a RemoveVariable() or ClearVariableList()
  1879. */
  1880. void BlockDestructor(const char *name, const char *value, uint type, uint flags,
  1881.     void *param)
  1882. {
  1883.     UNREF_PARAM(name);
  1884.     UNREF_PARAM(type);
  1885.     UNREF_PARAM(flags);
  1886.  
  1887.     /* in debug versions of htp, a special "secret" option can be set to */
  1888.     /* keep BLOCK temporary files around, for later inspection ... */
  1889.     /* not real useful in release versions */
  1890. #if DEBUG
  1891.     if(KEEPTEMP == FALSE)
  1892.     {
  1893.         remove(value);
  1894.     }
  1895.     else
  1896.     {
  1897.         HtpMsg(MSG_INFO, NULL, "DEBUG: keeping temporary file %s", value);
  1898.     }
  1899. #else
  1900.     /* simply delete the temporary file holding the block macro text */
  1901.     DEBUG_PRINT(("deleting file %s for block macro %s\n", value, name));
  1902.     remove(value);
  1903. #endif
  1904.  
  1905.     /* DEF macros use param */
  1906.     if(param != NULL)
  1907.     {
  1908.         FreeMemory(param);
  1909.     }
  1910. }
  1911.  
  1912. uint BlockProcessor(TASK *task, HTML_MARKUP *htmlMarkup, char **newPlaintext)
  1913. {
  1914.     char *name;
  1915.     char *plaintext;
  1916.     char newfile[MAX_PATHNAME_LEN];
  1917.     HTML_MARKUP newHtml;
  1918.     TEXTFILE blockFile;
  1919.     BOOL result;
  1920.     static uint blockDepth = 0;
  1921.     uint markupType;
  1922.     BOOL blockMacro;
  1923.     uint macroType;
  1924.     const char *macroTypeName;
  1925.     const char *openTag;
  1926.     const char *closeTag;
  1927.     const char *defName;
  1928.     char *param;
  1929.  
  1930.     UNREF_PARAM(newPlaintext);
  1931.  
  1932.     /* first: is this a BLOCK macro or a DEF macro?  This function is */
  1933.     /* overloaded to handle both types, and must internally change its */
  1934.     /* functionality for each type */
  1935.     /* if this is false, this is a DEF macro */
  1936.     if(stricmp(htmlMarkup->tag, "BLOCK") == 0)
  1937.     {
  1938.         blockMacro = TRUE;
  1939.         macroType = VAR_TYPE_BLOCK_MACRO;
  1940.         macroTypeName = "BLOCK";
  1941.         openTag = "BLOCK";
  1942.         closeTag = "/BLOCK";
  1943.     }
  1944.     else
  1945.     {
  1946.         assert(stricmp(htmlMarkup->tag, "DEF") == 0);
  1947.         blockMacro = FALSE;
  1948.         macroType = VAR_TYPE_DEF_MACRO;
  1949.         macroTypeName = "DEF";
  1950.         openTag = "DEF";
  1951.         closeTag = "/DEF";
  1952.     }
  1953.  
  1954.     /* check markup */
  1955.     if(blockMacro == TRUE)
  1956.     {
  1957.         /* is a name specified? (only one name can be specified) */
  1958.         if(htmlMarkup->attribCount != 1)
  1959.         {
  1960.             HtpMsg(MSG_ERROR, task->infile, "bad BLOCK markup specified");
  1961.             return MARKUP_ERROR;
  1962.         }
  1963.  
  1964.         /* no extra varstore parameter for a block macro */
  1965.         param = NULL;
  1966.     }
  1967.     else
  1968.     {
  1969.         /* DEF requires at least one parameter */
  1970.         if(htmlMarkup->attribCount == 0)
  1971.         {
  1972.             HtpMsg(MSG_ERROR, task->infile, "bad DEF markup specified");
  1973.             return MARKUP_ERROR;
  1974.         }
  1975.  
  1976.         /* check that the NAME attribute is present */
  1977.         if(IsAttributeInMarkup(htmlMarkup, "NAME") == FALSE)
  1978.         {
  1979.             HtpMsg(MSG_ERROR, task->infile, "DEF requires NAME attribute");
  1980.             return MARKUP_ERROR;
  1981.         }
  1982.  
  1983.         if(MarkupAttributeValue(htmlMarkup, "NAME") == NULL)
  1984.         {
  1985.             HtpMsg(MSG_ERROR, task->infile, "DEF requires a NAME attribute to have a value");
  1986.             return MARKUP_ERROR;
  1987.         }
  1988.  
  1989.         /* if OPTION is specified, squirrel it away with the macro */
  1990.         if(IsAttributeInMarkup(htmlMarkup, "OPTION") == TRUE)
  1991.         {
  1992.             if((param = DuplicateString(MarkupAttributeValue(htmlMarkup, "OPTION")))
  1993.                 == NULL)
  1994.             {
  1995.                 HtpMsg(MSG_ERROR, task->infile, "unable to create OPTION copy for DEF macro");
  1996.                 return MARKUP_ERROR;
  1997.             }
  1998.         }
  1999.         else
  2000.         {
  2001.             param = NULL;
  2002.         }
  2003.     }
  2004.  
  2005.     /* create a temporary filename to save the text block into */
  2006.     if(CreateTempFilename(newfile, MAX_PATHNAME_LEN) == FALSE)
  2007.     {
  2008.         HtpMsg(MSG_ERROR, task->infile, "unable to generate temporary filename for %s macro",
  2009.             macroTypeName);
  2010.         FreeMemory(param);
  2011.         return MARKUP_ERROR;
  2012.     }
  2013.  
  2014.     /* try and create the temporary file */
  2015.     if(OpenFile(newfile, newfile, "w", &blockFile) == FALSE)
  2016.     {
  2017.         HtpMsg(MSG_ERROR, task->infile,
  2018.             "unable to create temporary file \"%s\" for %s macro", newfile,
  2019.             macroTypeName);
  2020.         FreeMemory(param);
  2021.         return MARKUP_ERROR;
  2022.     }
  2023.  
  2024.     /* need a private copy of the macro name */
  2025.     if(blockMacro == TRUE)
  2026.     {
  2027.         if((name = DuplicateString(htmlMarkup->attrib[0].name)) == NULL)
  2028.         {
  2029.             HtpMsg(MSG_ERROR, task->infile, "unable to allocate memory for %s macro",
  2030.                 macroTypeName);
  2031.             FreeMemory(param);
  2032.             CloseFile(&blockFile);
  2033.             return MARKUP_ERROR;
  2034.         }
  2035.     }
  2036.     else
  2037.     {
  2038.         defName = MarkupAttributeValue(htmlMarkup, "NAME");
  2039.         assert(defName != NULL);
  2040.  
  2041.         if((name = DuplicateString(defName)) == NULL)
  2042.         {
  2043.             HtpMsg(MSG_ERROR, task->infile, "unable to allocate memory for %s macro",
  2044.                 macroTypeName);
  2045.             FreeMemory(param);
  2046.             CloseFile(&blockFile);
  2047.             return MARKUP_ERROR;
  2048.         }
  2049.     }
  2050.  
  2051.     /* store the block file name and the block macro name as a variable */
  2052.     if(StoreVariable(task->varstore, name, newfile, macroType, VAR_FLAG_NONE,
  2053.         param, BlockDestructor) == FALSE)
  2054.     {
  2055.         HtpMsg(MSG_ERROR, task->infile, "unable to store macro information (out of memory?)");
  2056.         FreeMemory(name);
  2057.         FreeMemory(param);
  2058.         CloseFile(&blockFile);
  2059.         return MARKUP_ERROR;
  2060.     }
  2061.  
  2062.     /* the name is no longer used, dont free param it will be freed in */
  2063.     /* the block destructor */
  2064.     FreeMemory(name);
  2065.     name = NULL;
  2066.  
  2067.     /* start copying the file into the temporary file, looking for the */
  2068.     /* BLOCK or /BLOCK tag if block macro, DEF or /DEF otherwise ... */
  2069.     /* just squirrel away all other tags and text */
  2070.     for(;;)
  2071.     {
  2072.         result = ReadHtmlFile(task->infile, &blockFile, &plaintext, &markupType);
  2073.         if(result == ERROR)
  2074.         {
  2075.             CloseFile(&blockFile);
  2076.             return MARKUP_ERROR;
  2077.         }
  2078.         else if(result == FALSE)
  2079.         {
  2080.             /* end-of-file encountered before end-of-block */
  2081.             HtpMsg(MSG_ERROR, task->infile, "EOF encountered before %s macro declaration closed",
  2082.                 macroTypeName);
  2083.             CloseFile(&blockFile);
  2084.             return MARKUP_ERROR;
  2085.         }
  2086.  
  2087.         /* turn the plain text into HTML structure, but don't destroy */
  2088.         /* the plaintext, this will be used to copy into output file if */
  2089.         /* necessary */
  2090.         if(PlaintextToMarkup(plaintext, &newHtml) == FALSE)
  2091.         {
  2092.             /* memory alloc failed, most likely */
  2093.             HtpMsg(MSG_ERROR, task->infile, "could not process markup (out of memory?)");
  2094.             CloseFile(&blockFile);
  2095.             FreeMemory(plaintext);
  2096.             return MARKUP_ERROR;
  2097.         }
  2098.  
  2099.         if(markupType & MARKUP_TYPE_HTP)
  2100.         {
  2101.             /* check for embedded block declarations */
  2102.             if(IsMarkupTag(&newHtml, openTag))
  2103.             {
  2104.                 /* add to the block macro depth and continue */
  2105.                 blockDepth++;
  2106.             }
  2107.             else if(IsMarkupTag(&newHtml, closeTag) == TRUE)
  2108.             {
  2109.                 if(blockDepth > 0)
  2110.                 {
  2111.                     /* depth has decreased one */
  2112.                     blockDepth--;
  2113.                 }
  2114.                 else
  2115.                 {
  2116.                     /* found the end of the macro block */
  2117.                     DestroyMarkupStruct(&newHtml);
  2118.                     FreeMemory(plaintext);
  2119.                     break;
  2120.                 }
  2121.             }
  2122.         }
  2123.  
  2124.         /* if continuing, then the plaintext is put into the output stream */
  2125.         /* as-is ... there is no case where the processor continues scanning */
  2126.         /* but discards a markup */
  2127.         PutFileString(&blockFile, "%c%s%c", MARKUP_OPEN_DELIM(markupType),
  2128.             plaintext, MARKUP_CLOSE_DELIM(markupType));
  2129.  
  2130.         /* destroy the HTML markup, not needed any longer */
  2131.         DestroyMarkupStruct(&newHtml);
  2132.  
  2133.         /* destroy the plaintext buffer */
  2134.         FreeMemory(plaintext);
  2135.         plaintext = NULL;
  2136.     }
  2137.  
  2138.     CloseFile(&blockFile);
  2139.  
  2140.     return DISCARD_MARKUP;
  2141. }   
  2142.  
  2143. BOOL DiscardConditionalBlock(TEXTFILE *infile)
  2144. {
  2145.     char *plaintext;
  2146.     HTML_MARKUP htmlMarkup;
  2147.     BOOL result;
  2148.     uint embeddedConditionals;
  2149.     uint markupType;
  2150.  
  2151.     /* discard the block, looking for the matching ELSE or /IF statement */
  2152.     embeddedConditionals = 0;
  2153.     for(;;)
  2154.     {
  2155.         result = ReadHtmlFile(infile, NULL, &plaintext, &markupType);
  2156.         if(result == ERROR)
  2157.         {
  2158.             return FALSE;
  2159.         }
  2160.         else if(result == FALSE)
  2161.         {
  2162.             /* end-of-file before end-of-conditional ... error */
  2163.             HtpMsg(MSG_ERROR, infile, "EOF encountered before conditional closed");
  2164.             return FALSE;
  2165.         }
  2166.  
  2167.         if(PlaintextToMarkup(plaintext, &htmlMarkup) == FALSE)
  2168.         {
  2169.             /* memory alloc error */
  2170.             HtpMsg(MSG_ERROR, infile, "could not parse markup tag (out of memory?)");
  2171.             FreeMemory(plaintext);
  2172.             return FALSE;
  2173.         }
  2174.  
  2175.         /* FreeMemory the plaintext buffer, not needed any longer */
  2176.         FreeMemory(plaintext);
  2177.         plaintext = NULL;
  2178.  
  2179.         /* another conditional started? */
  2180.         if(IsMarkupTag(&htmlMarkup, "IF"))
  2181.         {
  2182.             embeddedConditionals++;
  2183.         }
  2184.         else if(IsMarkupTag(&htmlMarkup, "/IF"))
  2185.         {
  2186.             /* end of the conditional? */
  2187.             if(embeddedConditionals == 0)
  2188.             {
  2189.                 DestroyMarkupStruct(&htmlMarkup);
  2190.                 break;
  2191.             }
  2192.             embeddedConditionals--;
  2193.         }
  2194.         else if(IsMarkupTag(&htmlMarkup, "ELSE"))
  2195.         {
  2196.             /* start of TRUE block? */
  2197.             if(embeddedConditionals == 0)
  2198.             {
  2199.                 DestroyMarkupStruct(&htmlMarkup);
  2200.                 break;
  2201.             }
  2202.         }
  2203.  
  2204.         /* destroy and continue */
  2205.         DestroyMarkupStruct(&htmlMarkup);
  2206.     }
  2207.  
  2208.     return TRUE;
  2209. }   
  2210.  
  2211. uint BooleanProcessor(TASK *task, HTML_MARKUP *htmlMarkup, char **newPlaintext)
  2212. {
  2213.     static uint conditionalLevel = 0;
  2214.     const char *value;
  2215.     uint type;
  2216.     BOOL condTrue;
  2217.     HTML_ATTRIBUTE *attrib;
  2218.     BOOL notTagFound;
  2219.  
  2220.     UNREF_PARAM(newPlaintext);
  2221.  
  2222.     condTrue = FALSE;
  2223.  
  2224.     /* conditionalLevel keeps track of boolean depth */
  2225.     if(conditionalLevel == 0)
  2226.     {
  2227.         if((IsMarkupTag(htmlMarkup, "/IF")) || (IsMarkupTag(htmlMarkup, "ELSE")))
  2228.         {
  2229.             HtpMsg(MSG_ERROR, task->infile, "conditional block must start with IF tag");
  2230.             return MARKUP_ERROR;
  2231.         }
  2232.     }
  2233.  
  2234.     if(IsMarkupTag(htmlMarkup, "IF"))
  2235.     {
  2236.         conditionalLevel++;
  2237.  
  2238.         /* this is an ugly way to handle the IF-IF NOT test, but will need */
  2239.         /* be cleaned up in the future */
  2240.  
  2241.         /* should either be one or two attributes in markup */
  2242.         if(htmlMarkup->attribCount == 0)
  2243.         {
  2244.             HtpMsg(MSG_ERROR, task->infile, "no conditional to test");
  2245.             return MARKUP_ERROR;
  2246.         }
  2247.  
  2248.         if(htmlMarkup->attribCount > 2)
  2249.         {
  2250.             HtpMsg(MSG_ERROR, task->infile, "too many items in conditional expression");
  2251.             return MARKUP_ERROR;
  2252.         }
  2253.  
  2254.         /* find the attribute to evaluate and search for NOT attribute */
  2255.         notTagFound = FALSE;
  2256.         attrib = NULL;
  2257.         if(stricmp(htmlMarkup->attrib[0].name, "NOT") == 0)
  2258.         {
  2259.             /* check to make sure the second attribute is present */
  2260.             if(htmlMarkup->attribCount == 1)
  2261.             {
  2262.                 HtpMsg(MSG_ERROR, task->infile, "NOT listed, no conditional to test");
  2263.                 return MARKUP_ERROR;
  2264.             }
  2265.  
  2266.             notTagFound = TRUE;
  2267.             attrib = &htmlMarkup->attrib[1];
  2268.         }
  2269.         else if(htmlMarkup->attribCount == 2)
  2270.         {
  2271.             if(stricmp(htmlMarkup->attrib[1].name, "NOT") == 0)
  2272.             {
  2273.                 notTagFound = TRUE;
  2274.                 attrib = &htmlMarkup->attrib[0];
  2275.             }
  2276.             else
  2277.             {
  2278.                 /* this should have been the NOT expression */
  2279.                 HtpMsg(MSG_ERROR, task->infile, "too many conditionals to test");
  2280.                 return MARKUP_ERROR;
  2281.             }
  2282.         }
  2283.         else
  2284.         {
  2285.             attrib = &htmlMarkup->attrib[0];
  2286.         }
  2287.  
  2288.         /* get the macros associated value (NULL if macro not defined) */
  2289.         if((value = GetVariableValue(task->varstore, attrib->name)) != NULL)
  2290.         {
  2291.             type = GetVariableType(task->varstore, attrib->name);
  2292.         }
  2293.         else
  2294.         {
  2295.             type = VAR_TYPE_SET_MACRO;
  2296.         }
  2297.  
  2298.         /* if only a name is specified, only care if macro is defined */
  2299.         if(attrib->value == NULL)
  2300.         {
  2301.             condTrue = (value != NULL) ? TRUE : FALSE;
  2302.         }
  2303.         else
  2304.         {
  2305.             /* macro value comparison */
  2306.             if(type == VAR_TYPE_SET_MACRO)
  2307.             {
  2308.                 /* macro comparison (case-sensitive) */
  2309.                 condTrue = (strcmp(value, attrib->value) == 0) ? TRUE : FALSE;
  2310.             }
  2311.             else
  2312.             {
  2313.                 /* block macro, comparisons not allowed */
  2314.                 condTrue = FALSE;
  2315.             }
  2316.         }
  2317.  
  2318.         /* reverse conditional if NOT attribute found */
  2319.         if(notTagFound == TRUE)
  2320.         {
  2321.             condTrue = (condTrue == TRUE) ? FALSE : TRUE;
  2322.         }
  2323.  
  2324.         if(condTrue == TRUE)
  2325.         {
  2326.             /* simply discard the markup and let the file processor continue */
  2327.             return DISCARD_MARKUP;
  2328.         }
  2329.  
  2330.         /* discard the rest of the conditional block since this portion has */
  2331.         /* evaluated false */
  2332.         if(DiscardConditionalBlock(task->infile) == FALSE)
  2333.         {
  2334.             return MARKUP_ERROR;
  2335.         }
  2336.     }
  2337.     else if(IsMarkupTag(htmlMarkup, "ELSE"))
  2338.     {
  2339.         /* this can only occur if the associated conditional statement */
  2340.         /* evaluated TRUE, so the remaining block must be discarded */
  2341.         if(DiscardConditionalBlock(task->infile) == FALSE)
  2342.         {
  2343.             return MARKUP_ERROR;
  2344.         }
  2345.     }
  2346.     else
  2347.     {
  2348.         /* end of conditional */
  2349.         assert(conditionalLevel > 0);
  2350.         conditionalLevel--;
  2351.     }
  2352.  
  2353.     return DISCARD_MARKUP;
  2354. }   
  2355.  
  2356. uint CommentProcessor(TASK *task, HTML_MARKUP *htmlMarkup, char **newPlaintext)
  2357. {
  2358.     UNREF_PARAM(htmlMarkup);
  2359.     UNREF_PARAM(newPlaintext);
  2360.  
  2361.     /* authors ego-gratifying easter egg */
  2362.     /* put a final comment at the end of the output file */
  2363.     PutFileString(task->outfile, "\n\n<!-- HTML pre-processed by %s %d.%02d %s -->\n\n",
  2364.         PROGRAM_NAME, VER_MAJOR, VER_MINOR, VER_STAGE);
  2365.  
  2366.     return MARKUP_OKAY;
  2367. }
  2368.  
  2369. uint ConditionalWarning(TASK *task, HTML_MARKUP *htmlMarkup, char **newPlaintext)
  2370. {
  2371.     UNREF_PARAM(htmlMarkup);
  2372.     UNREF_PARAM(newPlaintext);
  2373.  
  2374.     HtpMsg(MSG_ERROR, task->infile, "IFNOT tag no longer recognized; use IF NOT instead");
  2375.     return MARKUP_ERROR;
  2376. }
  2377.  
  2378. uint PreProcessor(TASK *task, HTML_MARKUP *htmlMarkup, char **newPlaintext)
  2379. {
  2380.     UNREF_PARAM(htmlMarkup);
  2381.     UNREF_PARAM(newPlaintext);
  2382.  
  2383.     /* the CONDENSE option cannot be utilized inside a <PRE>...</PRE> */
  2384.     /* block, because there HTML DOES interpret CR's */
  2385.     if(CONDENSE)
  2386.     {
  2387.         if(IsMarkupTag(htmlMarkup, "PRE"))
  2388.         {
  2389.             AllowLinefeeds(task->outfile);
  2390.         }
  2391.         else if(IsMarkupTag(htmlMarkup, "/PRE"))
  2392.         {
  2393.             SuppressLinefeeds(task->outfile);
  2394.         }
  2395.     }
  2396.  
  2397.     return MARKUP_OKAY;
  2398. }
  2399.  
  2400. uint AltTextProcessor(TASK *task, HTML_MARKUP *htmlMarkup, char **newPlaintext)
  2401. {
  2402.     const char *imgName;
  2403.     const char *imgText;
  2404.  
  2405.     UNREF_PARAM(newPlaintext);
  2406.  
  2407.     /* requires at least a NAME parameter */
  2408.     if(IsAttributeInMarkup(htmlMarkup, "NAME") == FALSE)
  2409.     {
  2410.         HtpMsg(MSG_ERROR, task->infile, "ALTTEXT requires a NAME attribute");
  2411.         return MARKUP_ERROR;
  2412.     }
  2413.  
  2414.     /* get the relevant information */
  2415.     imgName = MarkupAttributeValue(htmlMarkup, "NAME");
  2416.     if(imgName == NULL)
  2417.     {
  2418.         HtpMsg(MSG_ERROR, task->infile, "NAME must be specified with a filename");
  2419.         return MARKUP_ERROR;
  2420.     }
  2421.  
  2422.     if(IsAttributeInMarkup(htmlMarkup, "TEXT") == TRUE)
  2423.     {
  2424.         imgText = MarkupAttributeValue(htmlMarkup, "TEXT");
  2425.         if(imgText == NULL)
  2426.         {
  2427.             HtpMsg(MSG_ERROR, task->infile, "TEXT must be specified with a value (\"\" is acceptable)");
  2428.             return MARKUP_ERROR;
  2429.         }
  2430.     }
  2431.     else
  2432.     {
  2433.         imgText = NULL;
  2434.     }
  2435.  
  2436.     /* try to find the graphic name in the ALTTEXT store */
  2437.     if(VariableExists(&altTextVarStore, imgName) == TRUE)
  2438.     {
  2439.         /* if no name specified, delete it from the store */
  2440.         if(imgText == NULL)
  2441.         {
  2442.             RemoveVariable(&altTextVarStore, imgName);
  2443.             HtpMsg(MSG_INFO, task->infile, "ALT text for image \"%s\" removed",
  2444.                 imgName);
  2445.             return DISCARD_MARKUP;
  2446.         }
  2447.  
  2448.         /* since it exists, simply re-storing the value will delete the */
  2449.         /* old one and replace with new one */
  2450.     }
  2451.     else if(imgText == NULL)
  2452.     {
  2453.         /* tried to delete an image not already in the store */
  2454.         /* just post a warning */
  2455.         HtpMsg(MSG_WARNING, task->infile, "attempted to delete image text not already defined");
  2456.         return DISCARD_MARKUP;
  2457.     }
  2458.  
  2459.     StoreVariable(&altTextVarStore, imgName, imgText, VAR_TYPE_ALTTEXT,
  2460.         VAR_FLAG_NONE, NULL, NULL);
  2461.  
  2462.     HtpMsg(MSG_INFO, task->infile, "ALT text for image \"%s\" set to \"%s\"",
  2463.         imgName, imgText);
  2464.  
  2465.     return DISCARD_MARKUP;
  2466. }
  2467.  
  2468. uint UndefProcessor(TASK *task, HTML_MARKUP *htmlMarkup, char **newPlaintext)
  2469. {
  2470.     uint ctr;
  2471.     const char *name;
  2472.  
  2473.     UNREF_PARAM(newPlaintext);
  2474.  
  2475.     /* need at least one attribute to undef */
  2476.     if(htmlMarkup->attribCount == 0)
  2477.     {
  2478.         HtpMsg(MSG_ERROR, task->infile, "UNDEF requires at least one name");
  2479.         return MARKUP_ERROR;
  2480.     }
  2481.  
  2482.     /* walk the list of attributes, deleting them as found */
  2483.     for(ctr = 0; ctr < htmlMarkup->attribCount; ctr++)
  2484.     {
  2485.         name = htmlMarkup->attrib[ctr].name;
  2486.  
  2487.         /* is it in the store? */
  2488.         if(VariableExists(task->varstore, name) == FALSE)
  2489.         {
  2490.             HtpMsg(MSG_ERROR, task->infile, "No metatag \"%s\" to undefine",
  2491.                 name);
  2492.             return MARKUP_ERROR;
  2493.         }
  2494.  
  2495.         /* only remove it if a DEF macro */
  2496.         if(GetVariableType(task->varstore, name) == VAR_TYPE_DEF_MACRO)
  2497.         {
  2498.             RemoveVariable(task->varstore, name);
  2499.         }
  2500.  
  2501.         HtpMsg(MSG_INFO, task->infile, "metatag \"%s\" removed", name);
  2502.     }
  2503.  
  2504.     return DISCARD_MARKUP;
  2505. }
  2506.  
  2507. #define MARKUP_PROCESSOR_COUNT          (17)
  2508. MARKUP_PROCESSORS markupProcessor[MARKUP_PROCESSOR_COUNT] =
  2509. {
  2510.     { "IMG", MARKUP_TYPE_HTML, ImageProcessor },
  2511.     { "OPT", MARKUP_TYPE_HTP, OptionProcessor },
  2512.     { "FILE", MARKUP_TYPE_HTP, FileProcessor },
  2513.     { "SET", MARKUP_TYPE_HTP, SetProcessor },
  2514.     { "BLOCK", MARKUP_TYPE_HTP, BlockProcessor },
  2515.     { "USE", MARKUP_TYPE_HTP, UseProcessor },
  2516.     { "IF", MARKUP_TYPE_HTP, BooleanProcessor },
  2517.     { "/IF", MARKUP_TYPE_HTP, BooleanProcessor },
  2518.     { "IFNOT", MARKUP_TYPE_HTP, ConditionalWarning },
  2519.     { "ELSE", MARKUP_TYPE_HTP, BooleanProcessor },
  2520.     { "/BODY", MARKUP_TYPE_HTML, CommentProcessor },
  2521.     { "UNSET", MARKUP_TYPE_HTP, UnsetProcessor },
  2522.     { "PRE", MARKUP_TYPE_HTML, PreProcessor },
  2523.     { "/PRE", MARKUP_TYPE_HTML, PreProcessor },
  2524.     { "ALTTEXT", MARKUP_TYPE_HTP, AltTextProcessor },
  2525.     { "DEF", MARKUP_TYPE_HTP, BlockProcessor },
  2526.     { "UNDEF", MARKUP_TYPE_HTP, UndefProcessor }
  2527. };
  2528.  
  2529. /*
  2530. // HTML processing
  2531. */
  2532.  
  2533. BOOL ExpandMacrosInString(TASK *task, const char *text, char *newText,
  2534.     uint newTextSize, BOOL *quoted, uint *changeCount)
  2535. {
  2536.     const char *expansion;
  2537.     char macro[MAX_VARVALUE_LEN];
  2538.     char *textPtr;
  2539.     uint insertStartPos;
  2540.     uint insertEndPos;
  2541.     uint newTextLength;
  2542.  
  2543.     assert(task != NULL);
  2544.     assert(text != NULL);
  2545.     assert(newText != NULL);
  2546.     assert(newTextSize > 0);
  2547.     assert(quoted != NULL);
  2548.     assert(changeCount != NULL);
  2549.  
  2550.     *changeCount = 0;
  2551.  
  2552.     insertStartPos = 0;
  2553.     insertEndPos = 0;
  2554.  
  2555.     /* optimization: if no '$' in text, no need to go futher */
  2556.     if(strchr(text, '$') == NULL)
  2557.     {
  2558. #if DEBUG
  2559.         expandSkipped++;
  2560. #endif
  2561.         return TRUE;
  2562.     }
  2563.  
  2564. #if DEBUG
  2565.     expandPerformed++;
  2566. #endif
  2567.  
  2568.     /* Allan Todd fix: only check buffer size when its known to be needed */
  2569.     assert(newTextSize >= strlen(text));
  2570.  
  2571.     /* copy the old text directly into the new text buffer, and use that */
  2572.     /* as working space */
  2573.     StringCopy(newText, text, newTextSize);
  2574.  
  2575.     /* loop repeatedly to evaluate the string until no more macros are found */
  2576.     for(;;)
  2577.     {
  2578.         macro[0] = NUL;
  2579.  
  2580.         /* search the value for a $-preceded macro name */
  2581.         textPtr = strchr(newText, '$');
  2582.         while(textPtr != NULL)
  2583.         {
  2584.             /* if only one contiguous '$' found, stop looking and process */
  2585.             if(textPtr[1] != '$')
  2586.             {
  2587.                 break;
  2588.             }
  2589.  
  2590.             textPtr = strchr(textPtr + 2, '$');
  2591.         }
  2592.  
  2593.         /* if nothing found, exit */
  2594.         if(textPtr == NULL)
  2595.         {
  2596.             break;
  2597.         }
  2598.  
  2599.         /* this is used later more than once, but could change each iteration */
  2600.         newTextLength = strlen(newText);
  2601.  
  2602.         /* process the macro */
  2603.  
  2604.         /* save the position to insert the macro */
  2605.         insertStartPos = textPtr - newText;
  2606.  
  2607.         /* skip the '$' */
  2608.         textPtr++;
  2609.  
  2610.         /* copy macro name into macro[] array and set up the text pointer */
  2611.         /* macro specified with braces? */
  2612.         if(*textPtr == '{')
  2613.         {
  2614.             char *endPtr;
  2615.  
  2616.             /* start of a macro */
  2617.             /* skip opening curly brace and find the closing curly brace */
  2618.             endPtr = strchr(++textPtr, '}');
  2619.             if(endPtr == NULL)
  2620.             {
  2621.                 HtpMsg(MSG_ERROR, task->infile, "ending brace not found in macro name");
  2622.                 return FALSE;
  2623.             }
  2624.  
  2625.             /* copy in the enclosed text */
  2626.             StringCopy(macro, textPtr, endPtr - textPtr + 1);
  2627.  
  2628.             /* set end insertion point */
  2629.             insertEndPos = endPtr - newText + 1;
  2630.         }
  2631.         else
  2632.         {
  2633.             /* start of macro, no braces */
  2634.             /* copy macro name until EOS */
  2635.             StringCopy(macro, textPtr, MAX_VARVALUE_LEN);
  2636.  
  2637.             /* set end insertion point */
  2638.             insertEndPos = newTextLength;
  2639.         }
  2640.  
  2641.         /* if no macro name found, stop */
  2642.         if(macro[0] == NUL)
  2643.         {
  2644.             break;
  2645.         }
  2646.  
  2647.         /* make sure variable exists in store */
  2648.         if(VariableExists(task->varstore, macro) != TRUE)
  2649.         {
  2650.             /* only a warning ... stop processing to prevent infinite */
  2651.             /* loop */
  2652.             HtpMsg(MSG_WARNING, task->infile, "unrecognized macro name \"%s\"",
  2653.                 macro);
  2654.             break;
  2655.         }
  2656.  
  2657.         /* block macros cannot be expanded inside of markup tags */
  2658.         if(GetVariableType(task->varstore, macro) == VAR_TYPE_BLOCK_MACRO)
  2659.         {
  2660.             HtpMsg(MSG_ERROR, task->infile, "cannot expand block macro \"%s\" inside a markup",
  2661.                 macro);
  2662.             return FALSE;
  2663.         }
  2664.  
  2665.         /* get the macros value and replace the attribute value */
  2666.         expansion = GetVariableValue(task->varstore, macro);
  2667.  
  2668.         if(expansion != NULL)
  2669.         {
  2670.             HtpMsg(MSG_INFO, task->infile, "expanding macro \"%s\" to \"%s\"",
  2671.                 macro, expansion);
  2672.         }
  2673.         else
  2674.         {
  2675.             HtpMsg(MSG_INFO, task->infile, "expanding macro \"%s\" to null text",
  2676.                 macro);
  2677.         }
  2678.  
  2679.         /* build a new string using the expanded macro */
  2680.  
  2681.         /* if the macro is embedded inside a larger string, */
  2682.         /* actually go through the rigamarole of piecing together */
  2683.         /* a new value string */
  2684.         if((insertStartPos != 0)
  2685.             || (insertEndPos != newTextLength))
  2686.         {
  2687.             char *dupString;
  2688.  
  2689.             /* make a copy of the text buffer */
  2690.             /* can't use DuplicateString() because extra buffer past EOS needed */
  2691.             dupString = AllocMemory(newTextSize);
  2692.             if(dupString == NULL)
  2693.             {
  2694.                 HtpMsg(MSG_ERROR, task->infile, "out of memory during macro expansion");
  2695.                 return FALSE;
  2696.             }
  2697.             StringCopy(dupString, newText, newTextSize);
  2698.  
  2699.             /* copy in the expanded macro at the insertion point */
  2700.             if(expansion != NULL)
  2701.             {
  2702.                 StringCopy(dupString + insertStartPos, expansion,
  2703.                     newTextSize - insertStartPos);
  2704.             }
  2705.             else
  2706.             {
  2707.                 dupString[insertStartPos] = NUL;
  2708.             }
  2709.  
  2710.             /* copy in any characters after the macro ended */
  2711.             strncat(dupString, newText + insertEndPos, newTextSize);
  2712.  
  2713.             /* copy it back to the old string buffer and free */
  2714.             StringCopy(newText, dupString, newTextSize);
  2715.             FreeMemory(dupString);
  2716.             dupString = NULL;
  2717.  
  2718.             /* let the surrounding text dictate quote marks */
  2719.         }
  2720.         else
  2721.         {
  2722.             /* since the macro is the entire value, no harm (and */
  2723.             /* more robust) to surround it by quotes */
  2724.             StringCopy(newText, expansion, MAX_VARVALUE_LEN);
  2725.             *quoted = TRUE;
  2726.         }
  2727.  
  2728.         /* increment the change count */
  2729.         *changeCount = *changeCount + 1;
  2730.  
  2731.         /* need to go back and re-evaluate the value for more macros */
  2732.     }
  2733.  
  2734.     /* guess what!  need to re-process the value again to strip the */
  2735.     /* double '$' ... this couldn't be done previously because the newly */
  2736.     /* single '$' would have been processed as macro as the loop went */
  2737.     /* back around to finish off the next dereferenced macro */
  2738.     textPtr = strchr(newText, '$');
  2739.     while(textPtr != NULL)
  2740.     {
  2741.         /* look for adjacent '$' */
  2742.         if(textPtr[1] == '$')
  2743.         {
  2744.             /* terminate the preceding characters */
  2745.             textPtr[1] = NUL;
  2746.  
  2747.             /* concatenate the rest of the string */
  2748.             memmove(textPtr + 1, textPtr + 2, strlen(textPtr + 2) + 1);
  2749.  
  2750.             /* increment the change count */
  2751.             *changeCount = *changeCount + 1;
  2752.  
  2753.             /* start looking at the beginning of the new string */
  2754.             textPtr = strchr(newText, '$');
  2755.             continue;
  2756.         }
  2757.  
  2758.         /* just keep looking otherwise */
  2759.         textPtr = strchr(textPtr + 1, '$');
  2760.     }
  2761.  
  2762.     return TRUE;
  2763. }
  2764.  
  2765. BOOL ExpandMacros(TASK *task, HTML_MARKUP *htmlMarkup)
  2766. {
  2767.     uint ctr;
  2768.     HTML_ATTRIBUTE *attrib;
  2769.     BOOL result;
  2770.     union
  2771.     {
  2772.         char newTag[MAX_VARNAME_LEN];
  2773.         char newName[MAX_VARNAME_LEN];
  2774.         char newValue[MAX_VARVALUE_LEN];
  2775.     } newText;
  2776.     BOOL quoted;
  2777.     BOOL changeCount;
  2778.  
  2779.     assert(task != NULL);
  2780.     assert(htmlMarkup != NULL);
  2781.  
  2782.     /* expand any macros in the tags */
  2783.     if(htmlMarkup->tag != NULL)
  2784.     {
  2785.         if(ExpandMacrosInString(task, htmlMarkup->tag, newText.newTag,
  2786.             MAX_VARNAME_LEN, "ed, &changeCount) != TRUE)
  2787.         {
  2788.             return FALSE;
  2789.         }
  2790.  
  2791.         if(changeCount > 0)
  2792.         {
  2793.             ChangeMarkupTag(htmlMarkup, newText.newTag);
  2794.         }
  2795.     }
  2796.  
  2797.     /* do the same for all attributes, both name and value */
  2798.     result = TRUE;
  2799.     for(ctr = 0; ctr < htmlMarkup->attribCount; ctr++)
  2800.     {
  2801.         attrib = &htmlMarkup->attrib[ctr];
  2802.  
  2803.         if(attrib->name != NULL)
  2804.         {
  2805.             result = ExpandMacrosInString(task, attrib->name, newText.newName,
  2806.                 MAX_VARNAME_LEN, "ed, &changeCount);
  2807.             if(result != TRUE)
  2808.             {
  2809.                 break;
  2810.             }
  2811.  
  2812.             if(changeCount > 0)
  2813.             {
  2814.                 ChangeAttributeName(attrib, newText.newName);
  2815.             }
  2816.         }
  2817.  
  2818.  
  2819.         if(attrib->value != NULL)
  2820.         {
  2821.             quoted = attrib->quoted;
  2822.             result = ExpandMacrosInString(task, attrib->value, newText.newValue,
  2823.                 MAX_VARVALUE_LEN, "ed, &changeCount);
  2824.             if(result != TRUE)
  2825.             {
  2826.                 break;
  2827.             }
  2828.  
  2829.             if(changeCount > 0)
  2830.             {
  2831.                 ChangeAttributeValue(attrib, newText.newValue, quoted);
  2832.             }
  2833.         }
  2834.     } 
  2835.  
  2836.     return result;
  2837. }   
  2838.  
  2839. #define METATAG_MAX_OPTIONS                     (256)
  2840.  
  2841. uint ExpandMetatag(TASK *task, HTML_MARKUP *htmlMarkup)
  2842. {
  2843.     const char *options;
  2844.     char *optionCopy;
  2845.     FIND_TOKEN findToken;
  2846.     char *optionPtr;
  2847.     uint optionCount;
  2848.     uint ctr;
  2849.     uint optionCtr;
  2850.     const char *optionArray[METATAG_MAX_OPTIONS];
  2851.     const HTML_ATTRIBUTE *attrib;
  2852.     BOOL found;
  2853.     VARSTORE defVarstore;
  2854.     uint flag;
  2855.     VARSTORE *topVarstore;
  2856.     char defBlockName[64];
  2857.     const char *defFilename;
  2858.     const char *defName;
  2859.     TEXTFILE defFile;
  2860.     TASK newTask;
  2861.     BOOL result;
  2862.  
  2863.     /* first things first: find the tag in the metatag store */
  2864.     if(VariableExists(task->varstore, htmlMarkup->tag) == FALSE)
  2865.     {
  2866.         /* don't change a thing */
  2867.         return MARKUP_OKAY;
  2868.     }
  2869.  
  2870.     /* verify the macro in the store is a metatag definition */
  2871.     if(GetVariableType(task->varstore, htmlMarkup->tag) != VAR_TYPE_DEF_MACRO)
  2872.     {
  2873.         return MARKUP_OKAY;
  2874.     }
  2875.  
  2876.     /* get a pointer to the name */
  2877.     defName = htmlMarkup->tag;
  2878.  
  2879.     /* get the filename the DEF macro is held in */
  2880.     if((defFilename = GetVariableValue(task->varstore, defName)) == NULL)
  2881.     {
  2882.         /* this shouldnt be */
  2883.         HtpMsg(MSG_ERROR, task->infile, "DEF macro \"%s\" was not store properly",
  2884.             defName);
  2885.         return MARKUP_ERROR;
  2886.     }
  2887.  
  2888.     /* get options to compare against markups paramater list */
  2889.     options = GetVariableParam(task->varstore, defName);
  2890.  
  2891.     /* initialize a local variable store, even if its not used */
  2892.     InitializeVariableStore(&defVarstore);
  2893.  
  2894.     /* if NULL, then no options allowed */
  2895.     if(options == NULL)
  2896.     {
  2897.         if(htmlMarkup->attribCount > 0)
  2898.         {
  2899.             HtpMsg(MSG_ERROR, task->infile, "DEF macro \"%s\" does not specify any options",
  2900.                 defName);
  2901.             DestroyVariableStore(&defVarstore);
  2902.             return MARKUP_ERROR;
  2903.         }
  2904.  
  2905.         /* keep the current store context */
  2906.         topVarstore = task->varstore;
  2907.     }
  2908.     else
  2909.     {
  2910.         /* options should be space-delimited, use StringToken() */
  2911.         if((optionCopy = DuplicateString(options)) == NULL)
  2912.         {
  2913.             HtpMsg(MSG_ERROR, task->infile, "Unable to duplicate option macro (out of memory?)");
  2914.             DestroyVariableStore(&defVarstore);
  2915.             return MARKUP_ERROR;
  2916.         }
  2917.  
  2918.         /* build array of pointers to null-terminated option */
  2919.         optionCount = 0;
  2920.         optionPtr = StringFirstToken(&findToken, optionCopy, " ");
  2921.         while((optionPtr != NULL) && (optionCount < METATAG_MAX_OPTIONS))
  2922.         {
  2923.             /* ignore multiple spaces */
  2924.             if(*optionPtr != ' ')
  2925.             {
  2926.                 /* save a pointer to the token */
  2927.                 optionArray[optionCount++] = optionPtr;
  2928.             }
  2929.  
  2930.             optionPtr = StringNextToken(&findToken);
  2931.         }
  2932.  
  2933.         /* now, see if every paramater in the markup is also in the option list */
  2934.         /* (the reverse is not required) */
  2935.         for(ctr = 0; (attrib = MarkupAttribute(htmlMarkup, ctr)) != NULL; ctr++)
  2936.         {
  2937.             found = FALSE;
  2938.             for(optionCtr = 0; optionCtr < optionCount; optionCtr++)
  2939.             {
  2940.                 if(stricmp(optionArray[optionCtr], attrib->name) == 0)
  2941.                 {
  2942.                     found = TRUE;
  2943.                     break;
  2944.                 }
  2945.             }
  2946.  
  2947.             if(found == FALSE)
  2948.             {
  2949.                 HtpMsg(MSG_ERROR, task->infile,
  2950.                     "DEF macro \"%s\" does not accept a parameter named \"%s\"",
  2951.                     defName, attrib->name);
  2952.                 FreeMemory(optionCopy);
  2953.                 DestroyVariableStore(&defVarstore);
  2954.                 return MARKUP_ERROR;
  2955.             }
  2956.  
  2957.             /* since this is a good attribute, add it to the local store */
  2958.             flag = (attrib->quoted == TRUE) ? VAR_FLAG_QUOTED : VAR_FLAG_NONE;
  2959.             if(StoreVariable(&defVarstore, attrib->name, attrib->value,
  2960.                 VAR_TYPE_SET_MACRO, flag, NULL, NULL) == FALSE)
  2961.             {
  2962.                 HtpMsg(MSG_ERROR, task->infile,
  2963.                     "Unable to store local macro for metatag");
  2964.                 DestroyVariableStore(&defVarstore);
  2965.                 FreeMemory(optionCopy);
  2966.                 return MARKUP_ERROR;
  2967.             }
  2968.         }
  2969.  
  2970.         /* looks good, this is no longer needed */
  2971.         FreeMemory(optionCopy);
  2972.  
  2973.         /* make this the topmost context */
  2974.         PushVariableStoreContext(task->varstore, &defVarstore);
  2975.         topVarstore = &defVarstore;
  2976.     }
  2977.  
  2978.     /* expand the DEF macro like a block macro ... */
  2979.  
  2980.     /* set up the DEF macro name for user messages */
  2981.     sprintf(defBlockName, "Metatag \"%s\"", defName);
  2982.  
  2983.     /* open the file the macro is held in */
  2984.     if(OpenFile(defBlockName, defFilename, "r", &defFile) == FALSE)
  2985.     {
  2986.         HtpMsg(MSG_ERROR, task->infile,
  2987.             "unable to open temporary file \"%s\" for DEF macro \"%s\"",
  2988.             defFilename, defName);
  2989.         return MARKUP_ERROR;
  2990.     }
  2991.  
  2992.     HtpMsg(MSG_INFO, task->infile, "dereferencing metatag macro \"%s\"", defName);
  2993.  
  2994.     /* build a new task structure */
  2995.     newTask.infile = &defFile;
  2996.     newTask.outfile = task->outfile;
  2997.     newTask.sourceFilename = task->sourceFilename;
  2998.  
  2999.     /* re-user current variable store if no local store was made */
  3000.     newTask.varstore = topVarstore;
  3001.  
  3002.     /* process the new input file */
  3003.     result = ProcessTask(&newTask);
  3004.  
  3005.     /* remove the new context if necessary */
  3006.     if(topVarstore == &defVarstore)
  3007.     {
  3008.         assert(PeekVariableStoreContext(topVarstore) == topVarstore);
  3009.         PopVariableStoreContext(topVarstore);
  3010.     }
  3011.  
  3012.     /* no matter what, destroy the local store */
  3013.     DestroyVariableStore(&defVarstore);
  3014.  
  3015.     CloseFile(&defFile);
  3016.  
  3017.     return (result == TRUE) ? DISCARD_MARKUP : MARKUP_ERROR;
  3018. }
  3019.  
  3020. BOOL ProcessTask(TASK *task)
  3021. {
  3022.     char *newPlaintext;
  3023.     uint ctr;
  3024.     uint markupResult;
  3025.     HTML_MARKUP htmlMarkup;
  3026.     BOOL result;
  3027.     uint markupType;
  3028.  
  3029.     assert(task != NULL);
  3030.     assert(task->infile != NULL);
  3031.     assert(task->outfile != NULL);
  3032.     assert(task->varstore != NULL);
  3033.  
  3034.     for(;;)
  3035.     {
  3036.         result = ReadHtmlFile(task->infile, task->outfile, &newPlaintext,
  3037.             &markupType);
  3038.         if(result == ERROR)
  3039.         {
  3040.             /* problem reading in the file */
  3041.             return FALSE;
  3042.         }
  3043.         else if(result == FALSE)
  3044.         {
  3045.             /* end-of-file */
  3046.             break;
  3047.         }
  3048.  
  3049.         /* use the new markup plain text to build an HTML_MARKUP structure */
  3050.         if(PlaintextToMarkup(newPlaintext, &htmlMarkup) == FALSE)
  3051.         {
  3052.             HtpMsg(MSG_ERROR, task->infile, "could not parse markup tag (out of memory?)");
  3053.             FreeMemory(newPlaintext);
  3054.             return FALSE;
  3055.         }
  3056.  
  3057.         /* destroy the ORIGINAL plain text, not needed again */
  3058.         FreeMemory(newPlaintext);
  3059.         newPlaintext = NULL;
  3060.  
  3061.         /* give the default processor a chance to expand macros, etc. */
  3062.         if(ExpandMacros(task, &htmlMarkup) == FALSE)
  3063.         {
  3064.             /* problem encountered trying to expand macros */
  3065.             DestroyMarkupStruct(&htmlMarkup);
  3066.             return FALSE;
  3067.         }
  3068.  
  3069.         /* give the metatag processor a chance to expand metatags */
  3070.         /* this is a little strange, but if MARKUP_OKAY it means the the */
  3071.         /* metatag processor didnt recognize the tag, and therefore should */
  3072.         /* be handled by the other processors */
  3073.         if((markupResult = ExpandMetatag(task, &htmlMarkup)) == MARKUP_OKAY)
  3074.         {
  3075.             /* find the first processor that wants to do something with the */
  3076.             /* markup tag */
  3077.             for(ctr = 0; ctr < MARKUP_PROCESSOR_COUNT; ctr++)
  3078.             {
  3079.                 if(markupProcessor[ctr].markupType & markupType)
  3080.                 {
  3081.                     if(IsMarkupTag(&htmlMarkup, markupProcessor[ctr].tag))
  3082.                     {
  3083.                         assert(markupProcessor[ctr].markupFunc != NULL);
  3084.  
  3085.                         markupResult = markupProcessor[ctr].markupFunc(task,
  3086.                             &htmlMarkup, &newPlaintext);
  3087.  
  3088.                         break;
  3089.                     }
  3090.                 }
  3091.             }
  3092.         }
  3093.  
  3094.         /* unless the function requested to use its new markup string, */
  3095.         /* take the HTML_MARKUP structure and build a new markup */
  3096.         if((markupResult != NEW_MARKUP) && (markupResult != DISCARD_MARKUP))
  3097.         {
  3098.             if(MarkupToPlaintext(&htmlMarkup, &newPlaintext) == FALSE)
  3099.             {
  3100.                 HtpMsg(MSG_ERROR, task->infile, "unable to build plain text from markup (out of memory?)");
  3101.                 return FALSE;
  3102.             }
  3103.         }
  3104.  
  3105.         /* destroy the structure, now only interested in the markup string */
  3106.         DestroyMarkupStruct(&htmlMarkup);
  3107.  
  3108.         switch(markupResult)
  3109.         {
  3110.             case MARKUP_OKAY:
  3111.             {
  3112.                 /* add the markup to the output file as it should appear */
  3113.                 PutFileString(task->outfile, "%c%s%c", MARKUP_OPEN_DELIM(markupType),
  3114.                     newPlaintext, MARKUP_CLOSE_DELIM(markupType));
  3115.             }
  3116.             break;
  3117.  
  3118.             case NEW_MARKUP:
  3119.             case MARKUP_REPLACED:
  3120.             {
  3121.                 /* the markup has been replaced by a normal string */
  3122.                 PutFileString(task->outfile, "%s", newPlaintext);
  3123.             }
  3124.             break;
  3125.  
  3126.             case DISCARD_MARKUP:
  3127.             {
  3128.                 /* markup will not be included in final output */
  3129.             }
  3130.             break;
  3131.  
  3132.             case MARKUP_ERROR:
  3133.             {
  3134.                 /* (need to destroy plaintext buffer before exiting) */
  3135.                 FreeMemory(newPlaintext);
  3136.                 return FALSE;
  3137.             }
  3138.  
  3139.             default:
  3140.             {
  3141.                 FreeMemory(newPlaintext);
  3142.                 printf("%s: serious internal error\n", PROGRAM_NAME);
  3143.                 exit(1);
  3144.             }
  3145.         }
  3146.  
  3147.         /* free the plain text buffer and continue with processing */
  3148.         FreeMemory(newPlaintext);
  3149.         newPlaintext = NULL;
  3150.     }
  3151.  
  3152.     return TRUE;
  3153. }
  3154.  
  3155. BOOL FullyCheckDependencies(const char *in, const char *out)
  3156. {
  3157.     BOOL result;
  3158.     TEXTFILE infile;
  3159.     char *plaintext;
  3160.     char title[128];
  3161.     HTML_MARKUP markup;
  3162.     const char *includeFile;
  3163.     const char *imageFile;
  3164.     BOOL readResult;
  3165.     BOOL checkResult;
  3166.     uint markupType;
  3167.  
  3168.     assert(in != NULL);
  3169.     assert(out != NULL);
  3170.  
  3171.     if(DEPEND == FALSE)
  3172.     {
  3173.         /* outta here */
  3174.         return FALSE;
  3175.     }
  3176.  
  3177.     /* check if target file is completely up to date compared to input file */
  3178.     result = IsTargetUpdated(in, out);
  3179.     if(result == ERROR)
  3180.     {
  3181.         printf("%s: unable to get file information for file \"%s\"\n",
  3182.             PROGRAM_NAME, in);
  3183.         return ERROR;
  3184.     }
  3185.     else if(result == FALSE)
  3186.     {
  3187.         /* target is not updated */
  3188.         return FALSE;
  3189.     }
  3190.  
  3191.     /* because target is up to date, need to search dependency file for */
  3192.     /* FILE INCLUDE tags and check those files likewise */
  3193.  
  3194.     /* open file */
  3195.     sprintf(title, "Dependency check for %s", in);
  3196.     if(OpenFile(title, in, "r", &infile) == FALSE)
  3197.     {
  3198.         printf("%s: unable to open file \"%s\" for reading while checking dependencies\n",
  3199.             PROGRAM_NAME, in);
  3200.         return ERROR;
  3201.     }
  3202.  
  3203.     /* assume everything is hunky-dory unless otherwise discovered */
  3204.     checkResult = TRUE;
  3205.  
  3206.     /* get the next markup tag from the input file */
  3207.     while((readResult = ReadHtmlFile(&infile, NULL, &plaintext, &markupType)) != FALSE)
  3208.     {
  3209.         if(readResult == ERROR)
  3210.         {
  3211.             /* error occurred processing the HTML file */
  3212.             checkResult = ERROR;
  3213.             break;
  3214.         }
  3215.  
  3216.         /* check markup type ... only interested in htp markups currently */
  3217.         if((markupType & MARKUP_TYPE_HTP) == 0)
  3218.         {
  3219.             continue;
  3220.         }
  3221.  
  3222.         /* received a markup ... check if its an INCLUDE markup */
  3223.         PlaintextToMarkup(plaintext, &markup);
  3224.  
  3225.         /* do not need plaintext any further */
  3226.         FreeMemory(plaintext);
  3227.         plaintext = NULL;
  3228.  
  3229.         /* if FILE INCLUDE markup, get the filename specified */
  3230.         includeFile = NULL;
  3231.         imageFile = NULL;
  3232.         if(IsMarkupTag(&markup, "FILE"))
  3233.         {
  3234.             if(IsAttributeInMarkup(&markup, "INCLUDE"))
  3235.             {
  3236.                 includeFile = MarkupAttributeValue(&markup, "INCLUDE");
  3237.             }
  3238.             else if(IsAttributeInMarkup(&markup, "TEMPLATE"))
  3239.             {
  3240.                 includeFile = MarkupAttributeValue(&markup, "TEMPLATE");
  3241.             }
  3242.         }
  3243.         else if(IsMarkupTag(&markup, "IMG"))
  3244.         {
  3245.             if(IsAttributeInMarkup(&markup, "SRC"))
  3246.             {
  3247.                 imageFile = MarkupAttributeValue(&markup, "SRC");
  3248.             }
  3249.         }
  3250.         else if(IsMarkupTag(&markup, "OPT"))
  3251.         {
  3252.             if(IsAttributeInMarkup(&markup, "NODEPEND"))
  3253.             {
  3254.                 /* !! dependency checking disabled in source file ... since this */
  3255.                 /* can swing back and forth throughout the files, and its just */
  3256.                 /* a pain to track what is technically the last one set, */
  3257.                 /* if one is found, dependency checking is disabled and the */
  3258.                 /* targets are not considered updated */
  3259.                 /* this could be fixed with some work */
  3260.                 checkResult = FALSE;
  3261.                 DestroyMarkupStruct(&markup);
  3262.                 break;
  3263.             }
  3264.         }
  3265.  
  3266.         /* by default assume everything is up to date unless more information */
  3267.         /* is available through other files */
  3268.         result = TRUE;
  3269.  
  3270.         /* check include or image file timestamps */
  3271.         if(includeFile != NULL)
  3272.         {
  3273.             /* !! the accursed recursion strikes again */
  3274.             /* check the dependencies based on this new file */
  3275.             result = FullyCheckDependencies(includeFile, out);
  3276.         }
  3277.         else if(imageFile != NULL)
  3278.         {
  3279.             /* check the image files timestamp as part of dependency checking */
  3280.             if(FileExists(imageFile))
  3281.             {
  3282.                 result = IsTargetUpdated(imageFile, out);
  3283.             }
  3284.         }
  3285.  
  3286.         /* unneeded now */
  3287.         DestroyMarkupStruct(&markup);
  3288.  
  3289.         if(result != TRUE)
  3290.         {
  3291.             /* if FALSE, not up to date, no need to go further */
  3292.             /* if ERROR, need to stop and report to caller */
  3293.             checkResult = result;
  3294.             break;
  3295.         }
  3296.  
  3297.         /* otherwise, TRUE indicates that everything is okay, */
  3298.         /* so keep searching */
  3299.     }
  3300.  
  3301.     /* EOF encountered in the HTML input file ... target is updated */
  3302.     CloseFile(&infile);
  3303.  
  3304.     return checkResult;
  3305. }
  3306.  
  3307. BOOL ProcessFileByName(const char *in, const char *out)
  3308. {
  3309.     TEXTFILE infile;
  3310.     TEXTFILE outfile;
  3311.     TASK task;
  3312.     BOOL result;
  3313.     TEXTFILE project;
  3314.     const char *templateFile;
  3315.  
  3316.     assert(in != NULL);
  3317.     assert(out != NULL);
  3318.  
  3319.     /* before proceeding, find a local project default file */
  3320.     if(FileExists("htp.def"))
  3321.     {
  3322.         StringCopy(projectFilename, "htp.def", MAX_PATHNAME_LEN);
  3323.     }
  3324.     else
  3325.     {
  3326.         projectFilename[0] = NUL;
  3327.     }
  3328.  
  3329.     /* assume no processing required */
  3330.     result = TRUE;
  3331.  
  3332.     /* check the global and project default files first */
  3333.     if(*globalFilename != NUL)
  3334.     {
  3335.         if((result = FullyCheckDependencies(globalFilename, out)) == ERROR)
  3336.         {
  3337.             return FALSE;
  3338.         }
  3339.     }
  3340.  
  3341.     if((result == TRUE) && (*projectFilename != NUL))
  3342.     {
  3343.         if((result = FullyCheckDependencies(projectFilename, out)) == ERROR)
  3344.         {
  3345.             return FALSE;
  3346.         }
  3347.     }
  3348.  
  3349.     /* check the dependencies of the target file to see whether or not */
  3350.     /* to proceed ... the global and project default files are checked as well */
  3351.     if(result == TRUE)
  3352.     {
  3353.         if((result = FullyCheckDependencies(in, out)) == ERROR)
  3354.         {
  3355.             /* did not process the files */
  3356.             return FALSE;
  3357.         }
  3358.     }
  3359.  
  3360.     /* if TRUE, no need to go any further */
  3361.     if(result == TRUE)
  3362.     {
  3363.         /* explain why no processing required, and return as if processing */
  3364.         /* was completed */
  3365.         printf("%s: File \"%s\" is completely up to date.\n", PROGRAM_NAME,
  3366.             out);
  3367.         return TRUE;
  3368.     }
  3369.  
  3370.     /* continue, at least one file was found that requires out to be updated */
  3371.  
  3372.     /* initialize the project variable store and push it onto context */
  3373.     InitializeVariableStore(&projectVarStore);
  3374.     PushVariableStoreContext(&globalVarStore, &projectVarStore);
  3375.  
  3376.     /* initialize the ALT text store (used by the ALTTEXT tag) */
  3377.     InitializeVariableStore(&altTextVarStore);
  3378.  
  3379.     /* open the output file first, the project default file needs it */
  3380.     if(OpenFile(out, out, "w", &outfile) == FALSE)
  3381.     {
  3382.         printf("%s: unable to open file \"%s\" for writing\n", PROGRAM_NAME, out);
  3383.         DestroyVariableStore(&altTextVarStore);
  3384.         DestroyVariableStore(&projectVarStore);
  3385.         return FALSE;
  3386.     }
  3387.  
  3388.     if(CONDENSE)
  3389.     {
  3390.         /* suppress all linefeeds for this file, makes the HTML output smaller */
  3391.         SuppressLinefeeds(&outfile);
  3392.     }
  3393.  
  3394.     /* clear the task struct, in case there is no project file */
  3395.     memset(&task, 0, sizeof(TASK));
  3396.  
  3397.     if(projectFilename[0] != NUL)
  3398.     {
  3399.         /* process the default project file */
  3400.         if(OpenFile(projectFilename, projectFilename, "r", &project) == FALSE)
  3401.         {
  3402.             printf("%s: unable to open file \"%s\" for reading\n", PROGRAM_NAME,
  3403.                 projectFilename);
  3404.             CloseFile(&outfile);
  3405.             DestroyVariableStore(&projectVarStore);
  3406.             DestroyVariableStore(&altTextVarStore);
  3407.             return FALSE;
  3408.         }
  3409.  
  3410.         /* build a task structure */
  3411.         task.infile = &project;
  3412.         task.outfile = &outfile;
  3413.         task.varstore = &projectVarStore;
  3414.         task.sourceFilename = in;
  3415.  
  3416.         printf("%s: Processing default project file \"%s\" ...\n", PROGRAM_NAME,
  3417.             projectFilename);
  3418.  
  3419.         result = ProcessTask(&task);
  3420.  
  3421.         CloseFile(&project);
  3422.  
  3423.         if(result != TRUE)
  3424.         {
  3425.             if(PRECIOUS == FALSE)
  3426.             {
  3427.                 remove(out);
  3428.             }
  3429.  
  3430.             printf("%s: error during processing of default project file \"%s\"\n",
  3431.                 PROGRAM_NAME, projectFilename);
  3432.  
  3433.             CloseFile(&outfile);
  3434.             DestroyVariableStore(&projectVarStore);
  3435.             DestroyVariableStore(&altTextVarStore);
  3436.             return result;
  3437.         }
  3438.     }
  3439.  
  3440.     if(OpenFile(in, in, "r", &infile) == FALSE)
  3441.     {
  3442.         printf("%s: unable to open file \"%s\" for reading\n", PROGRAM_NAME, in);
  3443.         CloseFile(&outfile);
  3444.         DestroyVariableStore(&projectVarStore);
  3445.         DestroyVariableStore(&altTextVarStore);
  3446.         return FALSE;
  3447.     }
  3448.  
  3449.     if(InitializeLocalOption() == FALSE)
  3450.     {
  3451.         printf("%s: unable to initialize local option store\n", PROGRAM_NAME);
  3452.         CloseFile(&infile);
  3453.         CloseFile(&outfile);
  3454.         DestroyVariableStore(&projectVarStore);
  3455.         DestroyVariableStore(&altTextVarStore);
  3456.         return FALSE;
  3457.     }
  3458.  
  3459.     /* build a task structure */
  3460.     task.infile = &infile;
  3461.     task.outfile = &outfile;
  3462.     task.varstore = &projectVarStore;
  3463.     task.sourceFilename = in;
  3464.  
  3465.     printf("%s: Processing file \"%s\" to output file \"%s\" ...\n",
  3466.         PROGRAM_NAME, in, out);
  3467.  
  3468.     result = ProcessTask(&task);
  3469.  
  3470.     /* need to check for a template file */
  3471.     while((result == TRUE) && (VariableExists(&projectVarStore, VAR_TEMPLATE_NAME)))
  3472.     {
  3473.         /* go process it */
  3474.  
  3475.         /* done with this file, want to reuse struct */
  3476.         CloseFile(&infile);
  3477.  
  3478.         templateFile = GetVariableValue(&projectVarStore, VAR_TEMPLATE_NAME);
  3479.  
  3480.         if(OpenFile(templateFile, templateFile, "r", &infile) == FALSE)
  3481.         {
  3482.             printf("%s: unable to open template file \"%s\"\n",
  3483.                 PROGRAM_NAME, templateFile);
  3484.  
  3485.             CloseFile(&outfile);
  3486.             DestroyLocalOption();
  3487.             DestroyVariableStore(&altTextVarStore);
  3488.             DestroyVariableStore(&projectVarStore);
  3489.  
  3490.             return FALSE;
  3491.         }
  3492.  
  3493.         task.infile = &infile;
  3494.         task.outfile = &outfile;
  3495.         task.varstore = &projectVarStore;
  3496.         task.sourceFilename = in;
  3497.  
  3498.         printf("%s: Processing template file \"%s\" ...\n", PROGRAM_NAME,
  3499.             templateFile);
  3500.  
  3501.         result = ProcessTask(&task);
  3502.  
  3503.         /* because this template file can, legally, reference another */
  3504.         /* template file, remove the current variable and let the while loop */
  3505.         /* continue until no more template files are specified */
  3506.         /* yes, this can lead to infinite loops and such, but the syntax */
  3507.         /* shouldnt bar this, and after all, the same problem could exist if */
  3508.         /* the user kept doing circular FILE INCLUDEs */
  3509.         /* Pilot error! */
  3510.         RemoveVariable(&projectVarStore, VAR_TEMPLATE_NAME);
  3511.     }
  3512.  
  3513.     if(result == TRUE)
  3514.     {
  3515.         printf("%s: final output file \"%s\" successfully created\n\n",
  3516.             PROGRAM_NAME, outfile.name);
  3517.     }
  3518.     else
  3519.     {
  3520.         printf("\n%s: error encountered, file \"%s\" not completed\n\n",
  3521.             PROGRAM_NAME, outfile.name);
  3522.     }
  3523.  
  3524.     CloseFile(&outfile);
  3525.     CloseFile(&infile);
  3526.  
  3527.     /* destroy incomplete file if not configured elsewise */
  3528.     if(result != TRUE)
  3529.     {
  3530.         if(PRECIOUS == FALSE)
  3531.         {
  3532.             assert(out != NULL);
  3533.             remove(out);
  3534.         }
  3535.     }
  3536.  
  3537.     /* destroy the local options for these files */
  3538.     DestroyLocalOption();
  3539.  
  3540.     /* destroy the project store as well */
  3541.     DestroyVariableStore(&projectVarStore);
  3542.  
  3543.     /* destroy the ALT text store */
  3544.     DestroyVariableStore(&altTextVarStore);
  3545.  
  3546.     return result;
  3547. }
  3548.  
  3549. BOOL ProcessResponseFile(const char *resp)
  3550. {
  3551.     char textline[128];
  3552.     char defResp[MAX_PATHNAME_LEN];
  3553.     char newDirectory[MAX_PATHNAME_LEN];
  3554.     char oldDirectory[MAX_PATHNAME_LEN];
  3555.     TEXTFILE respfile;
  3556.     int result;
  3557.     char *in;
  3558.     char *out;
  3559.     char *ptr;
  3560.     BOOL useNewDir;
  3561.     BOOL respFileOpen;
  3562.     FIND_TOKEN findToken;
  3563.  
  3564.     assert(resp != NULL);
  3565.  
  3566.     useNewDir = FALSE;
  3567.  
  3568.     if(strchr(ALL_FILESYSTEM_DELIMITERS, resp[strlen(resp) - 1]) != NULL)
  3569.     {
  3570.         /* some tests as done to ensure that (a) newDirectory does not trail */
  3571.         /* with a directory separator and that (b) the separator is present */
  3572.         /* before appending the filename ... requirement (a) is a DOS issue */
  3573.  
  3574.         /* the response file is actually a directory the response file is */
  3575.         /* possibly kept in ... copy it to the newDirectory variable for */
  3576.         /* later use, but remove the trailing delimiter (MS-DOS issue) */
  3577.         strcpy(newDirectory, resp);
  3578.         newDirectory[strlen(newDirectory) - 1] = NUL;
  3579.  
  3580.         /* now, see if default response file is present */
  3581.         strcpy(defResp, newDirectory);
  3582.         strcat(defResp, DIR_DELIMITER_STRING);
  3583.         strcat(defResp, DEFAULT_RESPONSE_FILE);
  3584.  
  3585.         useNewDir = TRUE;
  3586.  
  3587.         respFileOpen = OpenFile(defResp, defResp, "r", &respfile);
  3588.     }
  3589.     else
  3590.     {
  3591.         respFileOpen = OpenFile(resp, resp, "r", &respfile);
  3592.     }
  3593.  
  3594.     if(respFileOpen == FALSE)
  3595.     {
  3596.         printf("%s: unable to open \"%s\" as a response file\n", PROGRAM_NAME,
  3597.             resp);
  3598.         return FALSE;
  3599.     }
  3600.  
  3601.     printf("%s: Processing response file \"%s\" ...\n", PROGRAM_NAME,
  3602.         respfile.name);
  3603.  
  3604.     /* processing a response file in another directory, change to that */
  3605.     /* directory before processing the files */
  3606.     if(useNewDir)
  3607.     {
  3608.         getcwd(oldDirectory, sizeof oldDirectory);
  3609.         chdir(newDirectory);
  3610.     }
  3611.  
  3612.     result = TRUE;
  3613.     do
  3614.     {
  3615.         if(GetFileLine(&respfile, textline, sizeof(textline)) == FALSE)
  3616.         {
  3617.             break;
  3618.         }
  3619.  
  3620.         in = NULL;
  3621.         out = NULL;
  3622.  
  3623.         /* walk tokens ... allow for tab character as token and ignore */
  3624.         /* multiple token characters between filenames */
  3625.         ptr = StringFirstToken(&findToken, textline, " \t");
  3626.         while(ptr != NULL)
  3627.         {
  3628.             /* is this just a repeated token? */
  3629.             if((*ptr == ' ') || (*ptr == '\t'))
  3630.             {
  3631.                 ptr = StringNextToken(&findToken);
  3632.                 continue;
  3633.             }
  3634.  
  3635.             /* found something ... like parsing the command-line, look for */
  3636.             /* options, then response files, then regular in and out filenames */
  3637.             if((*ptr == '-') || (*ptr == '/'))
  3638.             {
  3639.                 /* option */
  3640.                 ParseTextOption(ptr, OptionCallback, 0);
  3641.             }
  3642.             else if(*ptr == ';')
  3643.             {
  3644.                 /* comment, ignore the rest of the line */
  3645.                 break;
  3646.             }
  3647.             else if(in == NULL)
  3648.             {
  3649.                 in = ptr;
  3650.             }
  3651.             else if(out == NULL)
  3652.             {
  3653.                 out = ptr;
  3654.             }
  3655.             else
  3656.             {
  3657.                 /* hmm ... extra information on line */
  3658.                 HtpMsg(MSG_WARNING, &respfile, "extra option \"%s\" specified in response file, ignoring",
  3659.                     ptr);
  3660.             }
  3661.  
  3662.             ptr = StringNextToken(&findToken);
  3663.         }
  3664.  
  3665.         /* if in and out NULL, ignore the line entirely (all options or blank) */
  3666.         if((in == NULL) && (out == NULL))
  3667.         {
  3668.             continue;
  3669.         }
  3670.  
  3671.         if(out == NULL)
  3672.         {
  3673.             /* in is the response file ... recurse like theres no tomorrow */
  3674.             result = ProcessResponseFile(in);
  3675.             continue;
  3676.         }
  3677.  
  3678.         /* both in and out were specified, do it */
  3679.         result = ProcessFileByName(in, out);
  3680.     } while(result == TRUE);
  3681.  
  3682.     CloseFile(&respfile);
  3683.  
  3684.     /* restore the directory this all started in */
  3685.     if(useNewDir)
  3686.     {
  3687.         chdir(oldDirectory);
  3688.     }
  3689.  
  3690.     return result;
  3691. }   
  3692.  
  3693. BOOL ProcessDefaultFile(void)
  3694. {
  3695.     TASK defTask;
  3696.     TEXTFILE infile;
  3697.     TEXTFILE outfile;
  3698.     BOOL result;
  3699.  
  3700.     /* get the default filename */
  3701.     if(HtpDefaultFilename(globalFilename, MAX_PATHNAME_LEN) == FALSE)
  3702.     {
  3703.         /* nothing to do, but no error either */
  3704.         globalFilename[0] = NUL;
  3705.         return TRUE;
  3706.     }
  3707.  
  3708.     if(OpenFile(globalFilename, globalFilename, "r", &infile) == FALSE)
  3709.     {
  3710.         printf("%s: unable to open default file \"%s\"\n", PROGRAM_NAME,
  3711.             globalFilename);
  3712.         return FALSE;
  3713.     }
  3714.  
  3715.     /* use a null outfile because there is no file to write to */
  3716.     CreateNullFile(&outfile);
  3717.  
  3718.     /* build a task (just like any other) and process the file */
  3719.     /* use the global variable store to hold all the macros found */
  3720.     defTask.infile = &infile;
  3721.     defTask.outfile = &outfile;
  3722.     defTask.varstore = &globalVarStore;
  3723.     defTask.sourceFilename = globalFilename;
  3724.  
  3725.     printf("%s: Processing default file \"%s\" ... \n", PROGRAM_NAME,
  3726.         globalFilename);
  3727.  
  3728.     result = ProcessTask(&defTask);
  3729.  
  3730.     CloseFile(&infile);
  3731.     CloseFile(&outfile);
  3732.  
  3733.     return result;
  3734. }
  3735.  
  3736. int main(int argc, char *argv[])
  3737. {
  3738.     int result;
  3739.     uint ctr;
  3740.     char *in;
  3741.     char *out;
  3742.     char *resp;
  3743.  
  3744.     DisplayHeader();
  3745.  
  3746.     if(argc == 1)
  3747.     {
  3748.         usage();
  3749.         return 1;
  3750.     }
  3751.  
  3752.     /* initialize debugging */
  3753. #if DEBUG
  3754.     DebugInit("htpdeb.out");
  3755.     atexit(DebugTerminate);
  3756. #endif
  3757.  
  3758.     /* initialize the suballoc memory module */
  3759.     InitializeMemory();
  3760.     atexit(TerminateMemory);
  3761.  
  3762.     /* initialize global variable options */
  3763.     if(InitializeGlobalOption(OptionCallback, 0) == FALSE)
  3764.     {
  3765.         printf("%s: fatal error, unable to initialize internal options\n",
  3766.             PROGRAM_NAME);
  3767.         return 1;
  3768.     }
  3769.  
  3770.     in = NULL;
  3771.     out = NULL;
  3772.     resp = NULL;
  3773.  
  3774.     /* search command-line for options */
  3775.     for(ctr = 1; ctr < (uint) argc; ctr++)
  3776.     {
  3777.         if((*argv[ctr] == '-') || (*argv[ctr] == '/'))
  3778.         {
  3779.             /* command-line option specified */
  3780.             ParseTextOption(argv[ctr], OptionCallback, 0);
  3781.         }
  3782.         else if(*argv[ctr] == '@')
  3783.         {
  3784.             /* response file specified */
  3785.             resp = argv[ctr] + 1;
  3786.             if(*resp == NUL)
  3787.             {
  3788.                 resp = (char *) DEFAULT_RESPONSE_FILE;
  3789.             }
  3790.         }
  3791.         else if(in == NULL)
  3792.         {
  3793.             /* input file was specified */
  3794.             in = argv[ctr];
  3795.         }
  3796.         else if(out == NULL)
  3797.         {
  3798.             /* output file was specified */
  3799.             out = argv[ctr];
  3800.         }
  3801.         else
  3802.         {
  3803.             printf("%s: unknown argument \"%s\" specified\n",
  3804.                 PROGRAM_NAME, argv[ctr]);
  3805.             return 1;
  3806.         }
  3807.     }
  3808.     
  3809.     if(USAGE == TRUE)
  3810.     {
  3811.         usage();
  3812.         return 1;
  3813.     }
  3814.  
  3815.     if((in == NULL || out == NULL) && (resp == NULL))
  3816.     {
  3817.         usage();
  3818.         return 1;
  3819.     }
  3820.  
  3821.     /* initialize the global variable store before proceeding */
  3822.     if(InitializeVariableStore(&globalVarStore) != TRUE)
  3823.     {
  3824.         printf("%s: unable to initialize global variable store (out of memory?)\n",
  3825.             PROGRAM_NAME);
  3826.         return 1;
  3827.     }
  3828.  
  3829.     /* before reading in the response file or processing any files, handle */
  3830.     /* the default file, if there is one ... all of its macros are held in */
  3831.     /* the global variable store */
  3832.     ProcessDefaultFile();
  3833.  
  3834.     /* now, process the response file (if there is one) or the files */
  3835.     /* specified on the command-line */
  3836.     if(resp != NULL)
  3837.     {
  3838.         result = ProcessResponseFile(resp);
  3839.     }
  3840.     else
  3841.     {
  3842.         result = ProcessFileByName(in, out);
  3843.     }
  3844.  
  3845.     /* display varstore stats */
  3846.     DEBUG_PRINT(("Variable lookups=%u  string cmps=%u  missed string cmps=%u  cache hits=%u  hash calcs=%u\n",
  3847.         variableLookups, variableStringCompares, variableMissedStringCompares,
  3848.         variableCacheHits, variableHashCalcs));
  3849.  
  3850.     /* display macro expansion stats */
  3851.     DEBUG_PRINT(("Expansion skipped=%u  performed=%u\n", expandSkipped,
  3852.         expandPerformed));
  3853.  
  3854.     /* destroy the global variable store */
  3855.     DestroyVariableStore(&globalVarStore);
  3856.  
  3857.     /* destroy global option */
  3858.     DestroyGlobalOption();
  3859.  
  3860.     /* display suballoc stats */
  3861.     DEBUG_PRINT(("suballoc  total allocations=%u  free pool hits=%u  system heap allocs=%u\n",
  3862.         totalAllocations, freePoolHits, totalAllocations - freePoolHits));
  3863.  
  3864.     return (result == TRUE) ? 0 : 1;
  3865. }
  3866.  
  3867.